diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..045a3b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,115 @@ +# IDEA IDE +.idea/ + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +# MAC cache files +.DS_Store + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# vscode config +.vscode + +# JIRA plugin +atlassian-ide-plugin.xml + +#----------------------------------- +# Golang + +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ + +# GO compiled +/pkg/ + +# GO binaries +/bin/ + +# GO vendoring +/vendor/ + +# Project autogenerated documentation +/doc/ + +#----------------------------------- +# General + +out.log + +# generated binary +/main + + +# Autogenerated by go-bindata +# bindata.go + +# Autogenerated strings by stringer for const values (enums) +/src/**/*_string.go + +# Autogenerated files by go-enum for enum values. +/src/**/*_enum.go + +# Binary tools +tools/bin/ + +# Build folders and files +/build/ + +todo.md + +/logs/ + +conf.toml + +src/client/api/rest/docs + +.gitlab-ci-local + +*conf.toml + +diffs + +.linter_version.txt + +multiDBLogs/ diff --git a/.gitlab-ci-local-variables.yml b/.gitlab-ci-local-variables.yml new file mode 100644 index 0000000..c716bc8 --- /dev/null +++ b/.gitlab-ci-local-variables.yml @@ -0,0 +1,3 @@ +--- +MULTIDB_ACCESS_TOKEN: +USERNAME: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..7108b78 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,99 @@ +# Official framework image. Look for the different tagged releases at: +# https://hub.docker.com/r/library/node/tags/ +image: golang:1.24 + +default: + before_script: + - go clean -modcache + - git config --global url."https://$USERNAME:$MULTIDB_ACCESS_TOKEN@gitlab.com/quantex-exchange".insteadOf "https://gitlab.com/quantex-exchange" + - export GOPRIVATE="quantex.com.ar/multidb" + - go get quantex.com.ar/multidb@v1.2.2 + - make gogenerate + - make swag + - go mod vendor + artifacts: + when: always + paths: + - qfixpt.gz + +stages: + - lint + - build + +regular: + stage: build + needs: [] + except: + - master + - develop + - demo + - open-demo + script: + - go clean -cache + - make gogenerate + - make swag + - make test + - make check + - make build + - mv build/out/distribution/qfixpt.gz . + +develop: + stage: build + needs: [] + only: + - develop + script: + - make gogenerate + - make swag + - make test + - make only-build + - mv build/out/distribution/qfixpt.gz . + environment: + name: dev + url: https://dev.quantex.com.ar + +master: + stage: build + needs: [] + only: + - master + script: + - go clean -cache + - make gogenerate + - make swag + - make test + - make build + - mv build/out/distribution/qfixpt.gz . + environment: + name: prod + url: https://app.quantex.com.ar + +demo: + stage: build + needs: [] + only: + - demo + script: + - make test + - make only-build + - apt-get update + - apt-get install sshpass + - mv build/out/distribution/qfixpt.gz . + environment: + name: demo + url: https://demo.quantex.com.ar + +open-demo: + stage: build + needs: [] + only: + - open-demo + script: + - make test + - make only-build + - apt-get update + - apt-get install sshpass + - mv build/out/distribution/qfixpt.gz . + environment: + name: open-demo + url: https://open-demo.quantex.com.ar diff --git a/.gitlab/issue_templates/Default.md b/.gitlab/issue_templates/Default.md new file mode 100644 index 0000000..dee06dd --- /dev/null +++ b/.gitlab/issue_templates/Default.md @@ -0,0 +1,26 @@ + +## ℹ️ Issue Type + +- [ ] feature +- [ ] hotfix +- [ ] refactor +- [ ] improvement +- [ ] doc +- [ ] fix + +## ❌ Issue Description + +Summary_of_what_is_the_issue_and_include_screenshots_and_links_of_slack_to_follow_the_problem + +## βœ… Expected Behaviuor + +Explain_little_about_how_it_should_be_working + +## πŸ“‹ Steps to Reproduce + +What_steps_should_someone_take_to_reproduce_the_issue_include_screenshots_and_links + +## πŸ› Possible Impacts + +What_other_areas_in_our_code_might_be_affected_or_is_it_possible_that_something_else_broke_with_these_changes + diff --git a/.gitlab/merge_request_templates/Default.md b/.gitlab/merge_request_templates/Default.md new file mode 100644 index 0000000..9ddf8a1 --- /dev/null +++ b/.gitlab/merge_request_templates/Default.md @@ -0,0 +1,30 @@ + + +## πŸ› Reason to Be + +Double_click_to_replace_with_a_brief_summary_of_what_this_MR_does_including_a_summary_of_the_original_issue_and_include_screenshots_and_links_to_designs_if_this_MR_has_a_UI_component + +Resolves #Issue_number + +Related #Issue_number + + +## βœ… Expected Behaviuor + +Explain_little_about_how_is_it_working_in_the_current_MR + + +## πŸ“‹ How To Test + +What_steps_should_someone_take_to_test_these_changes_include_screenshots_and_links_to_mocks_for_any_ui_work + + +## ⚠️ Check List + +1. [ ] Title format _**SKL-33: Implement Logging using Qapix Endpoints**_ (PREFIX-Issue#: Message) +2. [ ] Run `make ci` at your end +3. [ ] Check the pipeline is **🟒 SUCCESS** +4. [ ] Set the Assignee and Reviewer +5. [ ] Create 2 PRs for ⚑ HOTFIX in master and develop️ + + diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0ae0e5d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,24 @@ +# Repository Guidelines + +## Project Structure & Module Organization +The service boots from `main.go`, and all Go packages live under `src`. Use `src/app` for orchestration, `src/domain` for entities and ports, `src/common` for shared helpers, `src/client/api/rest` for HTTP handlers and Swagger assets, and `src/cmd` for runnable entrypoints. Configuration samples live in `conf.toml`, `my_global_conf.toml`, and `res/`, while automation scripts sit in `build/` and `tools/`; vendor-locked dependencies remain in `vendor/` for reproducible builds. + +## Build, Test, and Development Commands +- `make dev` – runs the contributor checklist plus lint gate before coding. +- `make check` – installs toolchains and executes golangci-lint, gosec, revive, errcheck, and dependency audits. +- `make test` – calls `go test ./...`; use `go test ./src/... -run TestName` for a focused run. +- `make build e=dev` – compiles via `tools/build.sh`; switch `e` to `prod`, `demo`, or `open-demo`. +- `QUANTEX_ENVIRONMENT="dev" go run main.go -c -run service` – starts the service with text logs (omit `-c` for JSON). +- `make deploy e=` – pushes a build using the target defined in `build/deploy.sh`. + +## Coding Style & Naming Conventions +Run `make fmt` to apply gofumpt, gci, and goimports; Go’s formatter enforces tab indentation and canonical spacing. Follow idiomatic namingβ€”PascalCase for exported symbols, camelCase for internals, lowercase filenamesβ€”and keep package paths under `quantex.com/skeleton/`. Group imports as standard library, third-party, then `quantex.com`, and avoid formatting-only commits. + +## Testing Guidelines +Keep `*_test.go` files beside the code and prefer table-driven cases. Guard integration tests with `QUANTEX_ENVIRONMENT` so they hit the intended backend, and regenerate Swagger with `make swag` whenever REST contracts change. Spot-check concurrency with `go test -race ./src/` and store fixtures under package-level `testdata/` directories. + +## Commit & Pull Request Guidelines +Use `make branch t= u= n= m="summary"` to produce names such as `feature/jdoe/SKL-123/add_logger`. Write commit subjects in the imperative mood, referencing `SKL-###` when relevant. Pull requests must include a concise summary, linked issue, confirmation of `make test` and `make check`, and evidence (logs or screenshots) when altering responses or logging; call out configuration changes explicitly. + +## Configuration & Deployment Tips +Treat `conf.toml` and `my_global_conf.toml` as templates and override secrets via environment variables. Confirm build metadata with `make print-version` before deploying, and promote builds through `make deploy e=`. When exploring logging formats pass `-f text|json|tint1|tint2`, and follow the `README.md` guidance for multi-DB runs that require elevated permissions. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..78711b2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,496 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a Go microservice skeleton/template for Quantex projects. It implements a **Hexagonal Architecture** (Ports & Adapters) pattern with clear separation between domain logic, application orchestration, and infrastructure concerns. The skeleton supports multiple execution modes (REST API service, MQTT async messaging, external API integration examples) through a runner-based system. + +## Development Commands + +### Building and Running + +```bash +# Run the REST API service (text logs) +QUANTEX_ENVIRONMENT="dev" go run main.go -c -run service + +# Run the REST API service (JSON logs) +QUANTEX_ENVIRONMENT="dev" go run main.go -run service + +# Run with debug mode and specific log format +QUANTEX_ENVIRONMENT="dev" go run main.go -d -f tint1 -run service + +# Build for current platform +make build + +# Build for Linux (production) +make linux-build +``` + +### Available Runners + +The application supports multiple execution modes via the `-run` flag: +- `service` - Main REST API server (Gin framework) +- `async` - MQTT messaging example +- `external` - QApix external API integration example +- `tracerr` - Error handling demonstration +- `logger` - Logging format demonstration + +### Log Formats + +Use `-f` or `-logs-format` flag: +- `json` - Structured JSON logs (default, for production) +- `text` - Plain text logs +- `tint1` - Single-line colored logs (best for local development) +- `tint2` - Two-line colored logs + +### Testing and Linting + +```bash +# Run all tests +make test + +# Run tests for specific package +go test ./src/client/api/rest/... + +# Run single test +go test ./src/... -run TestFunctionName + +# Run checks and linters +make check + +# Format code (gofumpt, gci, goimports) +make fmt + +# Full CI pipeline (format, tidy, checks) +make ci + +# Regenerate Swagger docs +make swag + +# Run development workflow (checklist + check) +make dev +``` + +### Branch Management + +```bash +# Create new branch with naming convention +make branch t=feature u=jdoe n=39 m="add new endpoint" +# Creates: feature/jdoe/SKL-39/add_new_endpoint + +# Valid types: feature, hotfix, refactor, improvement, doc, fix +``` + +### Deployment + +```bash +# Deploy to environment (requires SSH alias in ~/.ssh/config) +make deploy e=dev +make deploy e=prod + +# First-time deployment +make first-deploy e=dev +``` + +## Architecture + +### Layer Structure + +The codebase follows Hexagonal Architecture with four distinct layers: + +``` +main.go (entry point) + ↓ +src/cmd/ (Command/Runner layer - bootstrap & execution modes) + ↓ +src/app/ (Application layer - configuration & wiring) + ↓ +src/client/ (Infrastructure layer - external integrations) + ↓ +src/domain/ (Domain layer - business logic & contracts) + ↓ +src/common/ (Cross-cutting concerns) +``` + +### Directory Organization + +- **`src/domain/`** - Core business models and interfaces (Asyncer, Notifier, UserDataProvider). Pure domain logic with no dependencies on infrastructure. + +- **`src/app/`** - Application-level configuration structures, version information, and global settings. Bridges domain and infrastructure. + +- **`src/client/`** - All infrastructure implementations: + - `api/rest/` - Gin HTTP server, controllers, routes, middleware (auth, CORS) + - `api/async/` - MQTT client for async messaging + - `config/` - TOML configuration loading + - `data/` - Data provider implementations + - `store/` - Database (MultiDB) and external API clients (QApix) + - `notify/` - Notification services (Google Chat, Slack) + - `res/` - Embedded resources + +- **`src/cmd/`** - Runners for different execution modes: + - `service/` - Main REST API service runner + - `example/` - Example runners (async, external, tracerr, logger) + - `base.go` - Runner abstraction and factory + +- **`src/common/`** - Shared utilities: + - `logger/` - Multi-format logging (JSON, text, tint) with Gin middleware + - `tracerr/` - Error handling with stack trace capture + - `securetoken/` - JWT token encryption/validation + +### REST API Architecture + +The REST API uses **Gin framework** with the following patterns: + +**Authentication Strategies:** +1. **Cookie-based** - HTTP-only secure cookie with Redis-backed session (20-minute TTL) +2. **Bearer token** - Format: `Bearer email:token` +3. **JWT token** - Service-to-service auth with permission validation + +**Middleware Chain:** +``` +Request β†’ Options (CORS) β†’ AuthRequired β†’ Role Check β†’ Handler +``` + +**Available middlewares in `src/client/api/rest/midlewares.go`:** +- `Options()` - CORS preflight handler +- `AuthRequired()` - Validates cookie or Authorization header +- `TokenAuth()` - Bearer token validation +- `JWTTokenAuth()` - JWT service token validation +- `CookieAuth()` - Session cookie validation +- Role-based: `SuperUser()`, `BackOfficeUser()`, `PartyAdmin()`, etc. + +**Key files:** +- `server.go` - Server initialization, Redis pool setup, trusted proxies +- `controller.go` - HTTP handlers (Login, Refresh, GetUser) +- `routes.go` - Route definitions with middleware chains +- `model.go` - Request/response DTOs +- `validator.go` - Input validation +- `traslator.go` - Response formatting + +### Async/MQTT Architecture + +Located in `src/client/api/async/`, uses Eclipse Paho MQTT client: + +- JWT-based authentication (token generated with service name and expiry) +- Auto-reconnect with keep-alive +- Topic structure: `{subdomain}/quantex/{topic}` +- Error notifications integrated +- Implements `domain.Asyncer` interface + +### Configuration System + +Configuration uses **TOML files** with two-level structure: + +1. **Global config** (`../global_conf.toml`) - Shared settings across services +2. **Service config** (`conf.toml`) - Service-specific settings + +Key configuration sections: +- `[MQTT]` - Async messaging settings +- `[Gin]` - HTTP server configuration +- `[Notify]` - Notification channels (Google, Slack) +- `[ExtAuth]` - External authentication providers + +Environment variable `QUANTEX_ENVIRONMENT` (required) determines runtime behavior: +- `dev` - Development mode, debug logging, relaxed CORS +- `demo`, `open-demo` - Demo environments +- `prod` - Production mode, release logging, strict security + +### Dependency Injection Pattern + +Dependencies flow from `main.go` β†’ runners β†’ infrastructure: + +```go +// Example from service runner +api := rest.New(userData, appStore, restConfig, notify) +// Dependencies: UserDataProvider, Store, Config, Notifier +``` + +All dependencies are interface-based for testability and loose coupling. + +### Error Handling + +Use `src/common/tracerr/` for errors that need stack traces: + +```go +import "quantex.com/skeleton/src/common/tracerr" + +// Instead of fmt.Errorf: +return tracerr.Errorf("failed to process: %w", err) + +// Errors automatically include stack traces +// Critical errors trigger notification via domain.Notifier +``` + +### Logging + +Access via standard library `log/slog`: + +```go +import "log/slog" + +slog.Info("message", "key", value) +slog.Error("error occurred", "error", err) +slog.Debug("debug info") // Only visible with -d flag +``` + +Logger integrates with Gin middleware for request logging and sends error-level logs to notifier. + +## Common Development Patterns + +### Adding a New REST Endpoint + +1. Add handler method to `src/client/api/rest/controller.go` +2. Define request/response models in `model.go` +3. Add route in `routes.go` with appropriate middleware +4. Update Swagger comments (use `// @` annotations) +5. Run `make swag` to regenerate docs +6. Test with `make test` + +### Adding a New Async Message Handler + +1. In your runner, subscribe to topic: +```go +asyncManager.Subscribe("my-topic", func(topic string, msg []byte) { + // Handle message +}) +``` +2. Publish messages: +```go +asyncManager.Publish("my-topic", myMessage) +``` + +### Adding a New Notifier + +1. Create package in `src/client/notify/{provider}/` +2. Implement `domain.Notifier` interface +3. Add configuration to `src/app/model.go` +4. Wire in `main.go` or use `notify/all/all.go` for multi-channel + +### Extending the Store + +Add repository methods to `src/client/store/manager.go`: + +```go +func (s *Store) GetSomething(ctx context.Context, id string) (*domain.Model, error) { + // Use s.db for database access +} +``` + +## Module Path Convention + +When initializing a new project from this skeleton: + +1. Update `go.mod` module path (e.g., `quantex.com/myservice`) +2. Replace all imports from `quantex.com/skeleton` to `quantex.com/myservice` +3. Update `PROJECT` and `DOMAIN` in `Makefile.common` +4. Update `ISSUE_PREFIX` in `Makefile.common` (e.g., `MYPROJ`) + +## Code Generation + +### Enums + +The project uses `go-enum` for type-safe enums. Files ending in `_enum.go` are generated: + +```bash +# Generate enums (run after modifying enum comments) +make gogenerate +``` + +Example enum definition in source files: +```go +// ENUM(Test, Web, Panic, Error) +type MessageChannel string +``` + +### Swagger Documentation + +Swagger docs are auto-generated from comments: + +```go +// @Summary Get user details +// @Description Retrieves user information by ID +// @Tags users +// @Accept json +// @Produce json +// @Param id path string true "User ID" +// @Success 200 {object} UserResponse +// @Router /users/{id} [get] +``` + +Always run `make swag` after modifying REST API contracts. + +## Important Conventions + +### Import Grouping + +Imports must be grouped (enforced by `gci`): +1. Standard library +2. Third-party packages +3. Quantex packages (prefixed with `quantex.com`) + +Run `make fmt` to auto-format. + +### Error Handling + +- Always check errors +- Use `tracerr` for errors that need debugging context +- Wrap errors with context: `tracerr.Errorf("context: %w", err)` +- Critical errors automatically notify via configured channels + +### Logging Best Practices + +- Use structured logging with key-value pairs +- Use appropriate log levels (Debug, Info, Warn, Error) +- Include relevant context in log messages +- Avoid logging sensitive information (tokens, passwords) + +### Testing + +- Place `*_test.go` files alongside source code +- Use table-driven tests for multiple cases +- Integration tests should check `QUANTEX_ENVIRONMENT` +- Run with race detector: `go test -race ./...` + +### Git Workflow + +- Create branches using `make branch` command +- Reference issue numbers in commits (e.g., `SKL-123`) +- Run `make ci` before pushing +- PRs must pass all checks +- Keep PRs under 500 lines of diff +- Follow the checklist in `checklist.md` + +## Security Considerations + +### Authentication + +Sessions are stored in Redis with 20-minute TTL. Cookie settings: +- `HttpOnly: true` - Prevents JavaScript access +- `Secure: true` in production - HTTPS only +- `SameSite: Strict` in production - CSRF protection + +### JWT Tokens + +Service-to-service JWT tokens use encryption and permission validation. See `src/common/securetoken/` for implementation. + +### Configuration Secrets + +- Never commit secrets to repository +- Use environment variables for sensitive data +- Configuration files in repo should be templates only +- Production secrets should be managed via deployment pipeline + +## Troubleshooting + +### "git state is not clean" Error + +The `make ci` and `make push` commands check for uncommitted changes. Commit or stash changes before running. + +### MultiDB Permission Errors + +MultiDB creates log folders with restrictive permissions. Either: +```bash +# Run with sudo +sudo QUANTEX_ENVIRONMENT="dev" go run main.go -run service + +# Or change permissions +sudo chmod 644 multiDB +sudo chmod 644 multiDB/2024_11_22 +``` + +### Swagger Generation Fails + +Ensure `src/client/api/rest/docs/docs.go` exists (even if empty): +```bash +mkdir -p src/client/api/rest/docs +echo "package docs" > src/client/api/rest/docs/docs.go +make swag +``` + +### MQTT Connection Issues + +Check configuration in `conf.toml`: +```toml +[MQTT] +Protocol = "wss" +URL = "async-non-prod.quantex.com.ar/mqtt/" +Subdomain = "dev" +Secret = "your-secret-here" +``` + +Ensure the secret matches the server configuration. + +## Templates + +### Ticket Creation Template: + +**Important:** In the console write the template in markdown format so the user can copy paste it directly. + +```markdown +## ℹ️ Issue Type + +- [ ] feature +- [ ] hotfix +- [ ] refactor +- [ ] improvement +- [ ] doc +- [ ] fix + +## ❌ Issue Description + +Summary_of_what_is_the_issue_and_include_screenshots_and_links_of_slack_to_follow_the_problem + +## βœ… Expected Behaviuor + +Explain_little_about_how_it_should_be_working + +## πŸ“‹ Steps to Reproduce + +What_steps_should_someone_take_to_reproduce_the_issue_include_screenshots_and_links + +## πŸ› Possible Impacts + +What_other_areas_in_our_code_might_be_affected_or_is_it_possible_that_something_else_broke_with_these_changes +``` + +## Code Review Description Template + +**Important:** In the console write the template in markdown format so the user can copy paste it directly. + +```markdown +## πŸ› Reason to Be + +Double_click_to_replace_with_a_brief_summary_of_what_this_MR_does_including_a_summary_of_the_original_issue_and_include_screenshots_and_links_to_designs_if_this_MR_has_a_UI_component + +Resolves #Issue_number + +Related #Issue_number + + +## βœ… Expected Behaviuor + +Explain_little_about_how_is_it_working_in_the_current_MR + + +## πŸ“‹ How To Test + +What_steps_should_someone_take_to_test_these_changes_include_screenshots_and_links_to_mocks_for_any_ui_work + + +## ⚠️ Check List + +1. [ ] Title format _**SKL-33: Implement Logging using Qapix Endpoints**_ (PREFIX-Issue#: Message) +2. [ ] Run `make ci` at your end +3. [ ] Check the pipeline is **🟒 SUCCESS** +4. [ ] Set the Assignee and Reviewer +5. [ ] Create 2 PRs for ⚑ HOTFIX in master and develop️ +``` + +## Related Documentation + +- README.md - Project initialization steps and examples +- checklist.md - PR review checklist +- AGENTS.md - Repository guidelines (if exists) +- `.golangci.yml` - Linter configuration +- `tools/check/` - Linter exclusions and settings diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b149483 --- /dev/null +++ b/Makefile @@ -0,0 +1,138 @@ +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +include Makefile.common + +.PHONY: help all clean dev check checklist tidy + +vendor: + go mod vendor + +push: vendor ci check-dirty ## Push the current branch to remote server + git push origin $$(git rev-parse --abbrev-ref HEAD) + +branch: ## Create a new branch and checkout it. Usage: make branch (interactive) + @./tools/branch.sh + +dev: checklist check + +# Note from Fede: +# See https://github.com/golang/go/issues/44129 +# If issues run: go env -w GOFLAGS=-mod=mod +# Run swag just in case +ci: swag fmt tidy check ## Run formatters (could modify the code), checks and linters + +check: check-static dependencies-check ## Run checks and linters + +download-versions: + curl --url "https://app.quantex.com.ar/linter_version.txt" -o $(linter_version_file) + +test: ## Run all the tests + go test ./... + +VALID_ENV_TYPES := prod dev demo open-demo +check-env: + @if [ "$(filter $(e),$(VALID_ENV_TYPES))" != "$(e)" ]; then \ + echo "\033[31mError: Invalid environment type $(e). Types can be: prod, dev, demo, open-demo"; \ + exit 1; \ + fi; + # set dev as default + $(eval e := dev) + +print-version: check-env ## Print the version of the latest build + OUT_PATH=$(DEFAULT_OUT_PATH) tools/print-version.sh $(e) + +build: check-env swag vendor only-build # Build a native version. Set e=environment: prod, dev, demo, open-demo + +only-build: check-env + @echo "Building for $(e) environment..." + env OUT_PATH=$(DEFAULT_OUT_PATH) tools/build.sh $(e) + +linux-build: check-env swag # Build a linux version for prod environment. Set e=environment: prod, dev, demo, open-demo + env OUT_PATH=$(DEFAULT_OUT_PATH) GOARCH=amd64 GOOS=linux tools/build.sh $(e) + +deploy: check-env # Deploy to remote server. Set e=environment: prod, dev, demo, open-demo + tools/deploy.sh $(e) + +fmt: download-versions # Apply the Go formatter to the code + cd tools/check; unset GOPATH; GOBIN=$$PWD/../bin go install mvdan.cc/gofumpt@$(call get_version,gofumpt); + @echo "running fmt..." + @echo $(GIT_TREE_STATE) + @echo "> running gofumpt..." + @tools/bin/gofumpt -l -w main.go 2>&1 + @tools/bin/gofumpt -l -w $(FILES) 2>&1 + +swag: download-versions + @echo "installing swag..." + cd tools/check; unset GOPATH; GOBIN=$$PWD/../bin go install github.com/swaggo/swag/cmd/swag@$(call get_version,swag); + @echo "running swag..." + rm -f src/client/api/rest/docs/swagger.json + rm -f src/client/api/rest/docs/swagger.yaml + rm -f src/client/api/rest/docs/docs.go + mkdir -p src/client/api/rest/docs + echo "package docs" > src/client/api/rest/docs/docs.go # This empty file is needed for the initial build. Then is overwritten by Swag. + tools/bin/swag init --parseDependency -g server.go -d src/client/api/rest -o src/client/api/rest/docs/ + +check-static: download-versions + @echo "installing golangci-lint..." + cd tools/check; unset GOPATH; GOBIN=$$PWD/../bin go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(call get_version,golangci-lint); + @echo "running golangci-lint..." + @echo $$($(PACKAGE_DIRECTORIES)) + + tools/bin/golangci-lint run -v -c tools/check/golangci.yml --timeout 5m0s ./... + +check-dirty: + $(if $(filter dirty,$(GIT_TREE_STATE)), $(error git state is not clean <$(GIT_TREE_STATE)>)) + +dependencies-check: download-versions + @echo "installing govulncheck..." + cd tools/check; unset GOPATH; GOBIN=$$PWD/../bin go install golang.org/x/vuln/cmd/govulncheck@$(call get_version,govulncheck); + @echo "running govulncheck..." + @tools/bin/govulncheck -show verbose ./... + +gogenerate: download-versions + @echo "installing go-enum..." + cd tools/check; unset GOPATH; GOBIN=$$PWD/../bin go install github.com/abice/go-enum@$(call get_version,go-enum); + @echo "running go generate ./.." + ./tools/check/check-gogenerate.sh + +tidy: + @echo "running go mod tidy..." + ./tools/check/check-tidy.sh + +server_coverage: +ifeq ($(TARGET), "") + $(GOBUILDCOVERAGE) $(RACE_FLAG) -ldflags '$(LDFLAGS) $(COVERAGE_SERVER_LDFLAGS) $(CHECK_FLAG)' -o ../bin/bitlab-coverage +else + $(GOBUILDCOVERAGE) $(RACE_FLAG) -ldflags '$(LDFLAGS) $(COVERAGE_SERVER_LDFLAGS) $(CHECK_FLAG)' -o '$(TARGET)' +endif + +checklist: + cat checklist.md + +linter_version_file = .linter_version.txt + +define get_version +$(shell grep "^$(1):" $(linter_version_file) | cut -d: -f2 | tr -d ' ') +endef + +init: ## Install dependencies to run the project + make swag + make gogenerate + go install ./... + +# Absolutely awesome: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html +help: + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}' + +.DEFAULT_GOAL := help diff --git a/Makefile.common b/Makefile.common new file mode 100644 index 0000000..d09de59 --- /dev/null +++ b/Makefile.common @@ -0,0 +1,62 @@ +# Copyright 2020 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +################################## +# Set this values for each project + +PROJECT := qfixpt +DOMAIN := quantex.com + +################################## + +ISSUE_PREFIX := QPT +GOPATH ?= $(shell go env GOPATH) + +P=8 + +GIT_TREE_STATE=$(shell (git status --porcelain | grep -q .) && echo dirty || echo clean) + +# Ensure GOPATH is set before running build process. +ifeq "$(GOPATH)" "" + $(error Please set the environment variable GOPATH before running `make`) +endif +FAIL_ON_STDOUT := awk '{ print } END { if (NR > 0) { exit 1 } }' + +CURDIR := $(shell pwd) +path_to_add := $(addsuffix /bin,$(subst :,/bin:,$(GOPATH))):$(PWD)/tools/bin +export PATH := $(path_to_add):$(PATH) + +GOBUILD := go build $(BUILD_FLAG) -tags codes +GOBUILDCOVERAGE := GOPATH=$(GOPATH) cd tidb-server; $(GO) test -coverpkg="../..." -c . +GOTEST := go test -p $(P) + +LINUX := "Linux" +MAC := "Darwin" +# PACKAGE_LIST := go list ./...| grep -vE "cmd|$(DOMAIN)\/$(PROJECT)\/tests" +PACKAGE_LIST := go list ./...| grep -vE "$(DOMAIN)\/$(PROJECT)\/tests" +PACKAGES ?= $$($(PACKAGE_LIST)) +PACKAGE_DIRECTORIES := $(PACKAGE_LIST) | sed 's|$(DOMAIN)/$(PROJECT)/||' | sed 's|$(DOMAIN)/$(PROJECT)||' +FILES := $$(find $$($(PACKAGE_DIRECTORIES)) -name "*.go") + +DEFAULT_OUT_PATH := "./build/out/distribution" + +RACE_FLAG = +ifeq ("$(WITH_RACE)", "1") + RACE_FLAG = -race + GOBUILD = GOPATH=$(GOPATH) $(GO) build +endif + +CHECK_FLAG = +ifeq ("$(WITH_CHECK)", "1") + CHECK_FLAG = $(TEST_LDFLAGS) +endif diff --git a/checklist.md b/checklist.md new file mode 100644 index 0000000..6476dd2 --- /dev/null +++ b/checklist.md @@ -0,0 +1,31 @@ +# Following the checklist saves the reviewers' time and gets your PR reviewed faster. + +# Self Review + +Have you reviewed every line of your changes by yourself? + +# Test + +Have you added enough test cases to cover the new feature or bug fix? Also, add comments to describe your test cases. + +# Naming + +Do function names keep consistent with its behavior? Is it easy to infer the function's behavior by its name? + +# Comment + +Is there any code that confuses the reviewer? Add comments on them! You'll be asked to do so anyway. Make sure there is +no syntax or spelling error in your comments. Some online syntax checking tools like Grammarly may be helpful. + +# Refactor + +Is there any way to refactor the code to make it more readable? If the refactoring touches a lot of existing code, send +another PR to do it. + +# Single Purpose + +Make sure the PR does only one thing and nothing else. + +# Diff Size + +Make sure the diff size is no more than 500, split it into small PRs if it is too large. diff --git a/docs/JWT_SERVICE_AUTHENTICATION.md b/docs/JWT_SERVICE_AUTHENTICATION.md new file mode 100644 index 0000000..4de157b --- /dev/null +++ b/docs/JWT_SERVICE_AUTHENTICATION.md @@ -0,0 +1,417 @@ +# JWT Service-to-Service Authentication + +## Overview + +Services authenticate with each other using JWT tokens. The system uses **two-factor authentication**: JWT signature validation + user credential validation against the database. + +--- + +## How It Works + +### Outgoing Request (Skeleton β†’ QApix) + +``` +1. Skeleton wants to call QApix +2. Creates JWT with: + - iss: "Skeleton" + - token: "franco:bpa31kniubroq28rpvg010" (from External.QApix.Token in config) + - iat: current timestamp +3. Signs JWT with QAPIX_QUANTEX_SECRET_KEY +4. Sends request with header: Authorization: {JWT} +``` + +### Incoming Request (QApix β†’ Skeleton) + +``` +1. Skeleton receives JWT from QApix +2. Validates JWT signature with SKELETON_QUANTEX_SECRET_KEY +3. Checks "QApix" is in AuthorizedServices config +4. Checks QApix has required permissions (currently FullAccess) +5. Extracts token claim: "franco:bpa31kniubroq28rpvg010" +6. Looks up user "franco" in database +7. Verifies password "bpa31kniubroq28rpvg010" matches +8. βœ… Request allowed +``` + +--- + +## Configuration + +### conf.toml + +```toml +# Enable/disable JWT authentication +EnableJWTAuth = true # Set to true to enable JWT authentication checks + +# Who we call (outgoing requests) +[External.QApix] +Name = "QApix" +Token = "franco:bpa31kniubroq28rpvg010" # Credentials we use to authenticate +Host = "https://qapix.example.com" +Port = "5001" + +# Who can call us (incoming requests) +[AuthorizedServices.QApix] +Name = "QApix" +Permissions = ["FullAccess"] # What QApix is allowed to do +# Token validated against user DB - not stored in config +``` + +**Configuration Options:** +- `EnableJWTAuth` (bool): Controls whether JWT authentication is enabled + - `true`: Enables JWT authentication and health check validation + - `false`: Disables JWT authentication features (default for local development) + +### Environment Variables + +```bash +# On Skeleton +export SKELETON_QUANTEX_SECRET_KEY="..." # Validates incoming JWTs +export QAPIX_QUANTEX_SECRET_KEY="..." # Signs outgoing JWTs to QApix + +# On QApix +export QAPIX_QUANTEX_SECRET_KEY="..." # Validates incoming JWTs +export SKELETON_QUANTEX_SECRET_KEY="..." # Signs outgoing JWTs to Skeleton +``` + +**Important**: Secret keys must match across services: +- `SKELETON_QUANTEX_SECRET_KEY` on Skeleton = `SKELETON_QUANTEX_SECRET_KEY` on QApix +- `QAPIX_QUANTEX_SECRET_KEY` on Skeleton = `QAPIX_QUANTEX_SECRET_KEY` on QApix + +--- + +## Security Layers + +| Layer | Description | Purpose | +|-------|-------------|---------| +| 1. JWT Signature | Validates HS256 signature | Proves caller has secret key | +| 2. Issuer Whitelist | Checks AuthorizedServices | Only known services allowed | +| 3. Permission Check | Validates service permissions | ReadOnly/ReadWrite/FullAccess | +| 4. User Validation | Checks token against user DB | Real credentials required | + +--- + +## Code Flow + +### Creating JWT +**Location**: `src/common/jwttoken/jwt.go` + +```go +func Encrypt(auth app.ExtAuth) (string, error) +``` + +**Usage**: +```go +auth := app.ExtAuth{ + Name: "QApix", + Token: "franco:bpa31kniubroq28rpvg010", +} +jwt, err := jwttoken.Encrypt(auth) +// Returns signed JWT token +``` + +### Validating JWT +**Location**: `src/common/jwttoken/jwt.go` + +```go +func Validate(token string, authServices map[string]AuthorizedService) (*AuthorizedService, error) +``` + +**Usage**: +```go +service, err := jwttoken.Validate(jwtToken, config.AuthorizedServices) +// Returns authorized service with token and permissions +``` + +### Middleware +**Location**: `src/client/api/rest/midlewares.go` + +```go +func (cont *Controller) JWTTokenAuth(reqToken string, ctx *gin.Context) +``` + +**Flow**: +1. Receives JWT from Authorization header +2. Calls `jwttoken.Validate()` to verify JWT and check permissions +3. Calls `validateServiceToken(token)` to verify user credentials in database +4. Sets `authorized_service` in Gin context +5. Proceeds to next handler + +--- + +## JWT Token Structure + +### Claims + +```json +{ + "iss": "Skeleton", // Issuer (who created the token) + "token": "franco:bpa31kniubroq28rpvg010", // User credentials + "iat": 1704481490 // Issued at (Unix timestamp) +} +``` + +### Header + +```json +{ + "alg": "HS256", // Algorithm (HMAC SHA-256) + "typ": "JWT" // Type +} +``` + +--- + +## Service Permissions + +Defined in `src/app/model.go`: + +```go +type ServicePermission int + +const ( + ServicePermissionReadOnly // Read-only access + ServicePermissionReadWrite // Read and write access + ServicePermissionFullAccess // Full access to all operations + ServicePermissionUndefined // No permissions +) +``` + +**Permission Check**: +```go +service.HasPermissions(app.ServicePermissionFullAccess) // Returns bool +``` + +--- + +## Key Benefits + +- βœ… **No credentials in config** - Token validated against user database +- βœ… **Recipient controls access** - Permissions defined by recipient, not sender +- βœ… **Two-factor auth** - Secret key + user credentials +- βœ… **Easy rotation** - Change user password β†’ JWT auth automatically updates +- βœ… **Audit trail** - Know which service user made each request +- βœ… **Single source of truth** - User credentials only in database +- βœ… **Configurable** - Enable/disable JWT auth per environment via `EnableJWTAuth` + +--- + +## Testing + +### Generate JWT Token + +```bash +# Set environment variable +export SKELETON_QUANTEX_SECRET_KEY="your-secret-key" + +# Run generator +cd tools +./generate-jwt.sh + +# Follow prompts: +# Issuer: Skeleton +# Service: SKELETON +# Token: franco:bpa31kniubroq28rpvg010 +``` + +### Test with curl + +```bash +# Store generated token +TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + +# Test endpoint +curl -H "Authorization: $TOKEN" \ + http://localhost:8097/api/v1/endpoint + +# Expected: 200 OK (if token valid and user exists) +# Expected: 401 Unauthorized (if token invalid or user not found) +``` + +### Test Scenarios + +| Scenario | Expected Result | +|----------|-----------------| +| Valid JWT + Valid user | βœ… 200 OK | +| Valid JWT + Invalid user | ❌ 401 Unauthorized | +| Invalid JWT signature | ❌ 401 Unauthorized | +| Unknown issuer | ❌ 401 Unauthorized | +| Insufficient permissions | ❌ 403 Forbidden | + +--- + +## Authentication Flow Diagram + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Skeleton β”‚ β”‚ QApix β”‚ +β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ + β”‚ 1. Create JWT β”‚ + β”‚ - iss: "Skeleton" β”‚ + β”‚ - token: "franco:password" β”‚ + β”‚ β”‚ + β”‚ 2. Sign with QAPIX_QUANTEX_SECRET_KEY β”‚ + β”‚ β”‚ + β”‚ 3. HTTP Request β”‚ + β”‚ Authorization: {JWT} β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€>β”‚ + β”‚ β”‚ + β”‚ 4. Decrypt JWTβ”‚ + β”‚ (QAPIX_QUANTEX_SECRET_KEY) β”‚ + β”‚ β”‚ + β”‚ 5. Validate claims β”‚ + β”‚ (iss, token) β”‚ + β”‚ β”‚ + β”‚ 6. Check AuthorizedServicesβ”‚ + β”‚ ("Skeleton" allowed?)β”‚ + β”‚ β”‚ + β”‚ 7. Check permissions β”‚ + β”‚ (FullAccess?) β”‚ + β”‚ β”‚ + β”‚ 8. Validate user in DB β”‚ + β”‚ (franco:password exists?)β”‚ + β”‚ β”‚ + β”‚ 9. Response (200 OK or 401/403) β”‚ + β”‚<────────────────────────────────────────────────── + β”‚ β”‚ +``` + +--- + +## Troubleshooting + +### Common Issues + +**"Invalid JWT"** +- Check secret key environment variable is set +- Verify secret keys match on both services + +**"Service not authorized"** +- Add service to `AuthorizedServices` in conf.toml +- Verify `Name` matches JWT `iss` claim + +**"Invalid credentials"** +- Check user exists in database +- Verify token format is `email:password` +- Ensure password matches user record + +**"Insufficient permissions"** +- Check service has required permission in `AuthorizedServices` +- Update `Permissions` array in conf.toml + +--- + +## Files Reference + +| File | Purpose | +|------|---------| +| `src/common/jwttoken/jwt.go` | JWT encryption, decryption, validation | +| `src/client/api/rest/midlewares.go` | HTTP middleware for JWT authentication | +| `src/client/api/rest/controller.go` | REST controllers including health check with JWT validation | +| `src/app/model.go` | AuthorizedService, ServicePermission types, EnableJWTAuth config | +| `src/client/api/rest/server.go` | REST API server configuration | +| `src/cmd/service/service.go` | Service runner that initializes JWT config | +| `conf.toml` | Service configuration | +| `tools/generate-jwt.sh` | JWT token generator for testing | + +--- + +## Migration Notes + +### From Old System + +**Before** (self-declared permissions): +```toml +[External.QApix] +Token = "franco:password" +Permissions = ["QApixAccess"] # ❌ Sender declares own permissions +``` + +**After** (recipient-controlled): +```toml +[External.QApix] +Token = "franco:password" +# βœ… No permissions - recipient decides + +[AuthorizedServices.QApix] +Permissions = ["FullAccess"] # βœ… Recipient controls access +``` + +### Deployment Steps + +1. Update `conf.toml` on both services + - Set `EnableJWTAuth = true` for production environments + - Set `EnableJWTAuth = false` for local development + - Configure `External` and `AuthorizedServices` sections +2. Set environment variables for secret keys +3. Restart services +4. Test with `generate-jwt.sh` +5. Verify health endpoint shows `jwtAuthentications: "ok"` +6. Monitor logs for authentication errors + +--- + +## Security Best Practices + +1. **Rotate Secret Keys Regularly** - Update environment variables periodically +2. **Use Strong Secrets** - Minimum 32 bytes, cryptographically random +3. **HTTPS Only** - Never send JWTs over unencrypted connections +4. **Monitor Auth Logs** - Track failed authentication attempts +5. **Principle of Least Privilege** - Use ReadOnly when possible +6. **Separate Service Users** - Don't share user credentials across services + +--- + +## Health Check Endpoint + +The `/health` endpoint provides service health status and optionally includes JWT authentication validation when enabled. + +### Response Structure + +**When `EnableJWTAuth = false`** (default for local development): +```json +{ + "status": "ok", + "build": "feature-branch", + "sha": "abc123def" +} +``` + +**When `EnableJWTAuth = true`** (production): +```json +{ + "status": "ok", + "build": "feature-branch", + "sha": "abc123def", + "jwtAuthentications": "ok" +} +``` + +**When JWT validation fails** (`EnableJWTAuth = true`): +```json +{ + "status": "degraded", + "build": "feature-branch", + "sha": "abc123def", + "jwtAuthentications": "error" +} +``` + +### Health Check Validation + +When `EnableJWTAuth = true`, the health endpoint validates JWT authentication by: +1. Attempting to fetch a test user from the database +2. Verifying the database connection is working +3. Returning `jwtAuthentications: "ok"` if successful +4. Returning `jwtAuthentications: "error"` and `status: "degraded"` if validation fails + +**Location**: `src/client/api/rest/controller.go:178` + +--- + +## Additional Resources + +- [JWT RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519) +- [golang-jwt/jwt](https://github.com/golang-jwt/jwt) +- Internal: `tools/README-JWT-GENERATOR.md` diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..231b61f --- /dev/null +++ b/go.mod @@ -0,0 +1,110 @@ +module quantex.com/qfixpt + +go 1.24.6 + +require ( + github.com/BurntSushi/toml v1.4.0 + github.com/eclipse/paho.mqtt.golang v1.5.1 + github.com/gin-gonic/gin v1.10.0 + github.com/golang-jwt/jwt/v5 v5.3.0 + github.com/gomodule/redigo v1.9.2 + github.com/mitchellh/panicwrap v1.0.0 + github.com/rs/zerolog v1.15.0 + github.com/sasha-s/go-deadlock v0.3.5 + github.com/satori/go.uuid v1.2.0 + github.com/shopspring/decimal v1.4.0 + github.com/swaggo/files v1.0.1 + github.com/swaggo/gin-swagger v1.6.0 + github.com/swaggo/swag v1.16.4 + quantex.com.ar/multidb v1.2.2 +) + +require ( + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect + github.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29 // indirect + github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bytedance/sonic v1.12.4 // indirect + github.com/bytedance/sonic/loader v0.2.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/codenotary/immudb v1.9.5 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.6 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/spec v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // 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.22.1 // indirect + github.com/goccy/go-json v0.10.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.3 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.3 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgtype v1.14.4 // indirect + github.com/jackc/pgx/v4 v4.18.3 // indirect + github.com/jackc/puddle v1.3.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/mapstructure v1.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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/o1egl/paseto v1.0.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/petermattis/goid v0.0.0-20241025130422-66cb2e6d7274 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.20.5 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.60.1 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/rs/xid v1.6.0 // indirect + github.com/sagikazarmark/locafero v0.6.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.7.0 // indirect + github.com/spf13/cobra v1.8.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.19.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/arch v0.12.0 // indirect + golang.org/x/crypto v0.45.0 // indirect + golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/term v0.37.0 // indirect + golang.org/x/text v0.31.0 // indirect + golang.org/x/tools v0.38.0 // indirect + google.golang.org/genproto v0.0.0-20241113154021-e0fbfb71d213 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241113154021-e0fbfb71d213 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241113154021-e0fbfb71d213 // indirect + google.golang.org/grpc v1.68.0 // indirect + google.golang.org/protobuf v1.35.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..040ae6a --- /dev/null +++ b/go.sum @@ -0,0 +1,736 @@ +cel.dev/expr v0.16.1/go.mod h1:AsGA5zb3WruAEQeQng1RZdGEXmBj0jvMWh6l5SnNuC8= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= +cloud.google.com/go/accessapproval v1.8.2/go.mod h1:aEJvHZtpjqstffVwF/2mCXXSQmpskyzvw6zKLvLutZM= +cloud.google.com/go/accesscontextmanager v1.9.2/go.mod h1:T0Sw/PQPyzctnkw1pdmGAKb7XBA84BqQzH0fSU7wzJU= +cloud.google.com/go/aiplatform v1.68.0/go.mod h1:105MFA3svHjC3Oazl7yjXAmIR89LKhRAeNdnDKJczME= +cloud.google.com/go/analytics v0.25.2/go.mod h1:th0DIunqrhI1ZWVlT3PH2Uw/9ANX8YHfFDEPqf/+7xM= +cloud.google.com/go/apigateway v1.7.2/go.mod h1:+weId+9aR9J6GRwDka7jIUSrKEX60XGcikX7dGU8O7M= +cloud.google.com/go/apigeeconnect v1.7.2/go.mod h1:he/SWi3A63fbyxrxD6jb67ak17QTbWjva1TFbT5w8Kw= +cloud.google.com/go/apigeeregistry v0.9.2/go.mod h1:A5n/DwpG5NaP2fcLYGiFA9QfzpQhPRFNATO1gie8KM8= +cloud.google.com/go/appengine v1.9.2/go.mod h1:bK4dvmMG6b5Tem2JFZcjvHdxco9g6t1pwd3y/1qr+3s= +cloud.google.com/go/area120 v0.9.2/go.mod h1:Ar/KPx51UbrTWGVGgGzFnT7hFYQuk/0VOXkvHdTbQMI= +cloud.google.com/go/artifactregistry v1.16.0/go.mod h1:LunXo4u2rFtvJjrGjO0JS+Gs9Eco2xbZU6JVJ4+T8Sk= +cloud.google.com/go/asset v1.20.3/go.mod h1:797WxTDwdnFAJzbjZ5zc+P5iwqXc13yO9DHhmS6wl+o= +cloud.google.com/go/assuredworkloads v1.12.2/go.mod h1:/WeRr/q+6EQYgnoYrqCVgw7boMoDfjXZZev3iJxs2Iw= +cloud.google.com/go/automl v1.14.2/go.mod h1:mIat+Mf77W30eWQ/vrhjXsXaRh8Qfu4WiymR0hR6Uxk= +cloud.google.com/go/baremetalsolution v1.3.2/go.mod h1:3+wqVRstRREJV/puwaKAH3Pnn7ByreZG2aFRsavnoBQ= +cloud.google.com/go/batch v1.11.2/go.mod h1:ehsVs8Y86Q4K+qhEStxICqQnNqH8cqgpCxx89cmU5h4= +cloud.google.com/go/beyondcorp v1.1.2/go.mod h1:q6YWSkEsSZTU2WDt1qtz6P5yfv79wgktGtNbd0FJTLI= +cloud.google.com/go/bigquery v1.64.0/go.mod h1:gy8Ooz6HF7QmA+TRtX8tZmXBKH5mCFBwUApGAb3zI7Y= +cloud.google.com/go/bigtable v1.33.0/go.mod h1:HtpnH4g25VT1pejHRtInlFPnN5sjTxbQlsYBjh9t5l0= +cloud.google.com/go/billing v1.19.2/go.mod h1:AAtih/X2nka5mug6jTAq8jfh1nPye0OjkHbZEZgU59c= +cloud.google.com/go/binaryauthorization v1.9.2/go.mod h1:T4nOcRWi2WX4bjfSRXJkUnpliVIqjP38V88Z10OvEv4= +cloud.google.com/go/certificatemanager v1.9.2/go.mod h1:PqW+fNSav5Xz8bvUnJpATIRo1aaABP4mUg/7XIeAn6c= +cloud.google.com/go/channel v1.19.1/go.mod h1:ungpP46l6XUeuefbA/XWpWWnAY3897CSRPXUbDstwUo= +cloud.google.com/go/cloudbuild v1.19.0/go.mod h1:ZGRqbNMrVGhknIIjwASa6MqoRTOpXIVMSI+Ew5DMPuY= +cloud.google.com/go/clouddms v1.8.2/go.mod h1:pe+JSp12u4mYOkwXpSMouyCCuQHL3a6xvWH2FgOcAt4= +cloud.google.com/go/cloudtasks v1.13.2/go.mod h1:2pyE4Lhm7xY8GqbZKLnYk7eeuh8L0JwAvXx1ecKxYu8= +cloud.google.com/go/compute v1.28.3/go.mod h1:HFlsDurE5DpQZClAGf/cYh+gxssMhBxBovZDYkEn/Og= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= +cloud.google.com/go/contactcenterinsights v1.15.1/go.mod h1:cFGxDVm/OwEVAHbU9UO4xQCtQFn0RZSrSUcF/oJ0Bbs= +cloud.google.com/go/container v1.41.0/go.mod h1:YL6lDgCUi3frIWNIFU9qrmF7/6K1EYrtspmFTyyqJ+k= +cloud.google.com/go/containeranalysis v0.13.2/go.mod h1:AiKvXJkc3HiqkHzVIt6s5M81wk+q7SNffc6ZlkTDgiE= +cloud.google.com/go/datacatalog v1.22.2/go.mod h1:9Wamq8TDfL2680Sav7q3zEhBJSPBrDxJU8WtPJ25dBM= +cloud.google.com/go/dataflow v0.10.2/go.mod h1:+HIb4HJxDCZYuCqDGnBHZEglh5I0edi/mLgVbxDf0Ag= +cloud.google.com/go/dataform v0.10.2/go.mod h1:oZHwMBxG6jGZCVZqqMx+XWXK+dA/ooyYiyeRbUxI15M= +cloud.google.com/go/datafusion v1.8.2/go.mod h1:XernijudKtVG/VEvxtLv08COyVuiYPraSxm+8hd4zXA= +cloud.google.com/go/datalabeling v0.9.2/go.mod h1:8me7cCxwV/mZgYWtRAd3oRVGFD6UyT7hjMi+4GRyPpg= +cloud.google.com/go/dataplex v1.19.2/go.mod h1:vsxxdF5dgk3hX8Ens9m2/pMNhQZklUhSgqTghZtF1v4= +cloud.google.com/go/dataproc/v2 v2.10.0/go.mod h1:HD16lk4rv2zHFhbm8gGOtrRaFohMDr9f0lAUMLmg1PM= +cloud.google.com/go/dataqna v0.9.2/go.mod h1:WCJ7pwD0Mi+4pIzFQ+b2Zqy5DcExycNKHuB+VURPPgs= +cloud.google.com/go/datastore v1.20.0/go.mod h1:uFo3e+aEpRfHgtp5pp0+6M0o147KoPaYNaPAKpfh8Ew= +cloud.google.com/go/datastream v1.11.2/go.mod h1:RnFWa5zwR5SzHxeZGJOlQ4HKBQPcjGfD219Qy0qfh2k= +cloud.google.com/go/deploy v1.24.0/go.mod h1:h9uVCWxSDanXUereI5WR+vlZdbPJ6XGy+gcfC25v5rM= +cloud.google.com/go/dialogflow v1.59.0/go.mod h1:PjsrI+d2FI4BlGThxL0+Rua/g9vLI+2A1KL7s/Vo3pY= +cloud.google.com/go/dlp v1.20.0/go.mod h1:nrGsA3r8s7wh2Ct9FWu69UjBObiLldNyQda2RCHgdaY= +cloud.google.com/go/documentai v1.35.0/go.mod h1:ZotiWUlDE8qXSUqkJsGMQqVmfTMYATwJEYqbPXTR9kk= +cloud.google.com/go/domains v0.10.2/go.mod h1:oL0Wsda9KdJvvGNsykdalHxQv4Ri0yfdDkIi3bzTUwk= +cloud.google.com/go/edgecontainer v1.4.0/go.mod h1:Hxj5saJT8LMREmAI9tbNTaBpW5loYiWFyisCjDhzu88= +cloud.google.com/go/errorreporting v0.3.1/go.mod h1:6xVQXU1UuntfAf+bVkFk6nld41+CPyF2NSPCyXE3Ztk= +cloud.google.com/go/essentialcontacts v1.7.2/go.mod h1:NoCBlOIVteJFJU+HG9dIG/Cc9kt1K9ys9mbOaGPUmPc= +cloud.google.com/go/eventarc v1.15.0/go.mod h1:PAd/pPIZdJtJQFJI1yDEUms1mqohdNuM1BFEVHHlVFg= +cloud.google.com/go/filestore v1.9.2/go.mod h1:I9pM7Hoetq9a7djC1xtmtOeHSUYocna09ZP6x+PG1Xw= +cloud.google.com/go/firestore v1.17.0/go.mod h1:69uPx1papBsY8ZETooc71fOhoKkD70Q1DwMrtKuOT/Y= +cloud.google.com/go/functions v1.19.2/go.mod h1:SBzWwWuaFDLnUyStDAMEysVN1oA5ECLbP3/PfJ9Uk7Y= +cloud.google.com/go/gkebackup v1.6.2/go.mod h1:WsTSWqKJkGan1pkp5dS30oxb+Eaa6cLvxEUxKTUALwk= +cloud.google.com/go/gkeconnect v0.11.2/go.mod h1:+Sj47chrbFMON1wjG6DA4KJKi85/7ON7GQZXEo0cbaQ= +cloud.google.com/go/gkehub v0.15.2/go.mod h1:8YziTOpwbM8LM3r9cHaOMy2rNgJHXZCrrmGgcau9zbQ= +cloud.google.com/go/gkemulticloud v1.4.1/go.mod h1:KRvPYcx53bztNwNInrezdfNF+wwUom8Y3FuJBwhvFpQ= +cloud.google.com/go/gsuiteaddons v1.7.2/go.mod h1:GD32J2rN/4APilqZw4JKmwV84+jowYYMkEVwQEYuAWc= +cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= +cloud.google.com/go/iap v1.10.2/go.mod h1:cClgtI09VIfazEK6VMJr6bX8KQfuQ/D3xqX+d0wrUlI= +cloud.google.com/go/ids v1.5.2/go.mod h1:P+ccDD96joXlomfonEdCnyrHvE68uLonc7sJBPVM5T0= +cloud.google.com/go/iot v1.8.2/go.mod h1:UDwVXvRD44JIcMZr8pzpF3o4iPsmOO6fmbaIYCAg1ww= +cloud.google.com/go/kms v1.20.1/go.mod h1:LywpNiVCvzYNJWS9JUcGJSVTNSwPwi0vBAotzDqn2nc= +cloud.google.com/go/language v1.14.2/go.mod h1:dviAbkxT9art+2ioL9AM05t+3Ql6UPfMpwq1cDsF+rg= +cloud.google.com/go/lifesciences v0.10.2/go.mod h1:vXDa34nz0T/ibUNoeHnhqI+Pn0OazUTdxemd0OLkyoY= +cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM= +cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= +cloud.google.com/go/managedidentities v1.7.2/go.mod h1:t0WKYzagOoD3FNtJWSWcU8zpWZz2i9cw2sKa9RiPx5I= +cloud.google.com/go/maps v1.14.1/go.mod h1:ZFqZS04ucwFiHSNU8TBYDUr3wYhj5iBFJk24Ibvpf3o= +cloud.google.com/go/mediatranslation v0.9.2/go.mod h1:1xyRoDYN32THzy+QaU62vIMciX0CFexplju9t30XwUc= +cloud.google.com/go/memcache v1.11.2/go.mod h1:jIzHn79b0m5wbkax2SdlW5vNSbpaEk0yWHbeLpMIYZE= +cloud.google.com/go/metastore v1.14.2/go.mod h1:dk4zOBhZIy3TFOQlI8sbOa+ef0FjAcCHEnd8dO2J+LE= +cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= +cloud.google.com/go/networkconnectivity v1.15.2/go.mod h1:N1O01bEk5z9bkkWwXLKcN2T53QN49m/pSpjfUvlHDQY= +cloud.google.com/go/networkmanagement v1.15.0/go.mod h1:Yc905R9U5jik5YMt76QWdG5WqzPU4ZsdI/mLnVa62/Q= +cloud.google.com/go/networksecurity v0.10.2/go.mod h1:puU3Gwchd6Y/VTyMkL50GI2RSRMS3KXhcDBY1HSOcck= +cloud.google.com/go/notebooks v1.12.2/go.mod h1:EkLwv8zwr8DUXnvzl944+sRBG+b73HEKzV632YYAGNI= +cloud.google.com/go/optimization v1.7.2/go.mod h1:msYgDIh1SGSfq6/KiWJQ/uxMkWq8LekPyn1LAZ7ifNE= +cloud.google.com/go/orchestration v1.11.1/go.mod h1:RFHf4g88Lbx6oKhwFstYiId2avwb6oswGeAQ7Tjjtfw= +cloud.google.com/go/orgpolicy v1.14.1/go.mod h1:1z08Hsu1mkoH839X7C8JmnrqOkp2IZRSxiDw7W/Xpg4= +cloud.google.com/go/osconfig v1.14.2/go.mod h1:kHtsm0/j8ubyuzGciBsRxFlbWVjc4c7KdrwJw0+g+pQ= +cloud.google.com/go/oslogin v1.14.2/go.mod h1:M7tAefCr6e9LFTrdWRQRrmMeKHbkvc4D9g6tHIjHySA= +cloud.google.com/go/phishingprotection v0.9.2/go.mod h1:mSCiq3tD8fTJAuXq5QBHFKZqMUy8SfWsbUM9NpzJIRQ= +cloud.google.com/go/policytroubleshooter v1.11.2/go.mod h1:1TdeCRv8Qsjcz2qC3wFltg/Mjga4HSpv8Tyr5rzvPsw= +cloud.google.com/go/privatecatalog v0.10.2/go.mod h1:o124dHoxdbO50ImR3T4+x3GRwBSTf4XTn6AatP8MgsQ= +cloud.google.com/go/pubsub v1.45.1/go.mod h1:3bn7fTmzZFwaUjllitv1WlsNMkqBgGUb3UdMhI54eCc= +cloud.google.com/go/pubsublite v1.8.2/go.mod h1:4r8GSa9NznExjuLPEJlF1VjOPOpgf3IT6k8x/YgaOPI= +cloud.google.com/go/recaptchaenterprise/v2 v2.18.0/go.mod h1:vnbA2SpVPPwKeoFrCQxR+5a0JFRRytwBBG69Zj9pGfk= +cloud.google.com/go/recommendationengine v0.9.2/go.mod h1:DjGfWZJ68ZF5ZuNgoTVXgajFAG0yLt4CJOpC0aMK3yw= +cloud.google.com/go/recommender v1.13.2/go.mod h1:XJau4M5Re8F4BM+fzF3fqSjxNJuM66fwF68VCy/ngGE= +cloud.google.com/go/redis v1.17.2/go.mod h1:h071xkcTMnJgQnU/zRMOVKNj5J6AttG16RDo+VndoNo= +cloud.google.com/go/resourcemanager v1.10.2/go.mod h1:5f+4zTM/ZOTDm6MmPOp6BQAhR0fi8qFPnvVGSoWszcc= +cloud.google.com/go/resourcesettings v1.8.2/go.mod h1:uEgtPiMA+xuBUM4Exu+ZkNpMYP0BLlYeJbyNHfrc+U0= +cloud.google.com/go/retail v1.19.1/go.mod h1:W48zg0zmt2JMqmJKCuzx0/0XDLtovwzGAeJjmv6VPaE= +cloud.google.com/go/run v1.6.1/go.mod h1:IvJOg2TBb/5a0Qkc6crn5yTy5nkjcgSWQLhgO8QL8PQ= +cloud.google.com/go/scheduler v1.11.2/go.mod h1:GZSv76T+KTssX2I9WukIYQuQRf7jk1WI+LOcIEHUUHk= +cloud.google.com/go/secretmanager v1.14.2/go.mod h1:Q18wAPMM6RXLC/zVpWTlqq2IBSbbm7pKBlM3lCKsmjw= +cloud.google.com/go/security v1.18.2/go.mod h1:3EwTcYw8554iEtgK8VxAjZaq2unFehcsgFIF9nOvQmU= +cloud.google.com/go/securitycenter v1.35.2/go.mod h1:AVM2V9CJvaWGZRHf3eG+LeSTSissbufD27AVBI91C8s= +cloud.google.com/go/servicedirectory v1.12.2/go.mod h1:F0TJdFjqqotiZRlMXgIOzszaplk4ZAmUV8ovHo08M2U= +cloud.google.com/go/shell v1.8.2/go.mod h1:QQR12T6j/eKvqAQLv6R3ozeoqwJ0euaFSz2qLqG93Bs= +cloud.google.com/go/spanner v1.72.0/go.mod h1:mw98ua5ggQXVWwp83yjwggqEmW9t8rjs9Po1ohcUGW4= +cloud.google.com/go/speech v1.25.2/go.mod h1:KPFirZlLL8SqPaTtG6l+HHIFHPipjbemv4iFg7rTlYs= +cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +cloud.google.com/go/storagetransfer v1.11.2/go.mod h1:FcM29aY4EyZ3yVPmW5SxhqUdhjgPBUOFyy4rqiQbias= +cloud.google.com/go/talent v1.7.2/go.mod h1:k1sqlDgS9gbc0gMTRuRQpX6C6VB7bGUxSPcoTRWJod8= +cloud.google.com/go/texttospeech v1.10.0/go.mod h1:215FpCOyRxxrS7DSb2t7f4ylMz8dXsQg8+Vdup5IhP4= +cloud.google.com/go/tpu v1.7.2/go.mod h1:0Y7dUo2LIbDUx0yQ/vnLC6e18FK6NrDfAhYS9wZ/2vs= +cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io= +cloud.google.com/go/translate v1.12.2/go.mod h1:jjLVf2SVH2uD+BNM40DYvRRKSsuyKxVvs3YjTW/XSWY= +cloud.google.com/go/video v1.23.2/go.mod h1:rNOr2pPHWeCbW0QsOwJRIe0ZiuwHpHtumK0xbiYB1Ew= +cloud.google.com/go/videointelligence v1.12.2/go.mod h1:8xKGlq0lNVyT8JgTkkCUCpyNJnYYEJVWGdqzv+UcwR8= +cloud.google.com/go/vision/v2 v2.9.2/go.mod h1:WuxjVQdAy4j4WZqY5Rr655EdAgi8B707Vdb5T8c90uo= +cloud.google.com/go/vmmigration v1.8.2/go.mod h1:FBejrsr8ZHmJb949BSOyr3D+/yCp9z9Hk0WtsTiHc1Q= +cloud.google.com/go/vmwareengine v1.3.2/go.mod h1:JsheEadzT0nfXOGkdnwtS1FhFAnj4g8qhi4rKeLi/AU= +cloud.google.com/go/vpcaccess v1.8.2/go.mod h1:4yvYKNjlNjvk/ffgZ0PuEhpzNJb8HybSM1otG2aDxnY= +cloud.google.com/go/webrisk v1.10.2/go.mod h1:c0ODT2+CuKCYjaeHO7b0ni4CUrJ95ScP5UFl9061Qq8= +cloud.google.com/go/websecurityscanner v1.7.2/go.mod h1:728wF9yz2VCErfBaACA5px2XSYHQgkK812NmHcUsDXA= +cloud.google.com/go/workflows v1.13.2/go.mod h1:l5Wj2Eibqba4BsADIRzPLaevLmIuYF2W+wfFBkRG3vU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= +github.com/aead/chacha20poly1305 v0.0.0-20170617001512-233f39982aeb/go.mod h1:UzH9IX1MMqOcwhoNOIjmTQeAxrFgzs50j4golQtXXxU= +github.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29 h1:1DcvRPZOdbQRg5nAHt2jrc5QbV0AGuhDdfQI6gXjiFE= +github.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29/go.mod h1:UzH9IX1MMqOcwhoNOIjmTQeAxrFgzs50j4golQtXXxU= +github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw= +github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us= +github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bytedance/sonic v1.12.4 h1:9Csb3c9ZJhfUWeMtpCDCq6BUoH5ogfDFLUgQ/jG+R0k= +github.com/bytedance/sonic v1.12.4/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E= +github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +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/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/codenotary/immudb v1.9.5 h1:z4DdsDLalQFVcuOktLw2H95BjFnvFL4TxY2tkYwOkkk= +github.com/codenotary/immudb v1.9.5/go.mod h1:+Sex0kDu5F1hE+ydm9p+mpZixjlSeBqrgUZUjNayrNg= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE= +github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnvMg4d7nvT/wl9WgVXn3Q8= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc= +github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +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 v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/gizak/termui/v3 v3.1.0/go.mod h1:bXQEBkJpzxUAKf0+xq9MSWAvWZlE7c+aidmyFlkYTrY= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +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.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= +github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/gomodule/redigo v1.9.2 h1:HrutZBLhSIU8abiSfW8pj8mPhOyMYjZT/wcA4/L9L9s= +github.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.17.0/go.mod h1:Hbb13e3/WtqQ8U5hLGkek9gJvBLasHuPFI0UEGfnQ10= +github.com/hashicorp/consul/api v1.28.2/go.mod h1:KyzqzgMEya+IZPcD65YFoOVAgPpbfERu4I/tzG6/ueE= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/influxdata/influxdb-client-go/v2 v2.13.0/go.mod h1:k+spCbt9hcvqvUiz0sr5D8LolXHqAAOfPw9v/RIRHl4= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.14.4 h1:fKuNiCumbKTAIxQwXfB/nsrnkEI6bPJrrSiMKgbJ2j8= +github.com/jackc/pgtype v1.14.4/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= +github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jaswdr/faker v1.16.0/go.mod h1:x7ZlyB1AZqwqKZgyQlnqEG8FDptmHlncA5u2zY/yi6w= +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/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +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/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +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/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +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/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +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.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +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/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/goveralls v0.0.11/go.mod h1:gU8SyhNswsJKchEV93xRQxX6X3Ei4PJdQk/6ZHvrvRk= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/panicwrap v1.0.0 h1:67zIyVakCIvcs69A0FGfZjBdPleaonSgGlXRSRlb6fE= +github.com/mitchellh/panicwrap v1.0.0/go.mod h1:pKvZHwWrZowLUzftuFq7coarnxbBXU4aQh3N0BJOeeA= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo= +github.com/nats-io/nats.go v1.34.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= +github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo= +github.com/o1egl/paseto v1.0.0 h1:bwpvPu2au176w4IBlhbyUv/S5VPptERIA99Oap5qUd0= +github.com/o1egl/paseto v1.0.0/go.mod h1:5HxsZPmw/3RI2pAwGo1HhOOwSdvBpcuVzO7uDkm+CLU= +github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/ory/go-acc v0.2.8/go.mod h1:iCRZUdGb/7nqvSn8xWZkhfVrtXRZ9Wru2E5rabCjFPI= +github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/peterh/liner v1.2.1/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/petermattis/goid v0.0.0-20241025130422-66cb2e6d7274 h1:qli3BGQK0tYDkSEvZ/FzZTi9ZrOX86Q6CIhKLGc489A= +github.com/petermattis/goid v0.0.0-20241025130422-66cb2e6d7274/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= +github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/pseudomuto/protoc-gen-doc v1.4.1/go.mod h1:exDTOVwqpp30eV/EDPFLZy3Pwr2sn6hBC1WIYH/UbIg= +github.com/pseudomuto/protokit v0.2.1/go.mod h1:gt7N5Rz2flBzYafvaxyIxMZC0TTF5jDZfRnw25hAAyo= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0 h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/crypt v0.19.0/go.mod h1:c6vimRziqqERhtSe0MhIvzE1w54FrCHtrXb5NH/ja78= +github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= +github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU= +github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/schollz/progressbar/v2 v2.15.0/go.mod h1:UdPq3prGkfQ7MOzZKlDRpYKcFqEMczbD7YmbPgpzKMI= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +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/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +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.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= +github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= +github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= +github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= +github.com/takama/daemon v0.12.0/go.mod h1:PFDPquCi+3LI5PpAKS/8LvJBHTfkdsEXfGtANGx9hH4= +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.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.etcd.io/etcd/api/v3 v3.5.12/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4= +go.etcd.io/etcd/client/pkg/v3 v3.5.12/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4= +go.etcd.io/etcd/client/v2 v2.305.12/go.mod h1:aQ/yhsxMu+Oht1FOupSr60oBvcS9cKXHrzBpDsPTf9E= +go.etcd.io/etcd/client/v3 v3.5.12/go.mod h1:tSbBCakoWmmddL+BKVAJHa9km+O/E+bumDe9mSbPiqw= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg= +golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-20211025201205-69cdffdb9359/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-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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +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/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/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.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/tools/cmd/cover v0.1.0-deprecated/go.mod h1:hMDiIvlpN1NoVgmjLjUJE9tMHyxHjFX7RuQ+rW12mSA= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.171.0/go.mod h1:Hnq5AHm4OTMt2BUVjael2CWZFD6vksJdWCWiUAmjC9o= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20241113154021-e0fbfb71d213 h1:aK1CSq3+WIXZj6XcWhK0fAb303nF8sta2eca0DSARwQ= +google.golang.org/genproto v0.0.0-20241113154021-e0fbfb71d213/go.mod h1:Q5m6g8b5KaFFzsQFIGdJkSJDGeJiybVenoYFMMa3ohI= +google.golang.org/genproto/googleapis/api v0.0.0-20241113154021-e0fbfb71d213 h1:cNftAhx0Q32f3Fz2+BLargfsMD6pGINE+/mUZneTMyk= +google.golang.org/genproto/googleapis/api v0.0.0-20241113154021-e0fbfb71d213/go.mod h1:Yo94eF2nj7igQt+TiJ49KxjIH8ndLYPZMIRSiRcEbg0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241113154021-e0fbfb71d213 h1:L+WcQXqkyf5MX6g7AudgYEuJjmYbqSRkTmJqGfAPw+Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241113154021-e0fbfb71d213/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= +google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +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-20210107192922-496545a6307b/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= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +quantex.com.ar/multidb v1.2.2 h1:vYZkOI/srWFlfUk8ZsbmcNp7OWiPWflBnaLYaPTPgAs= +quantex.com.ar/multidb v1.2.2/go.mod h1:aIxLGHeesPkmSocw/VSZBc+pGDmxx7QRUSd+TxjAFFc= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/linter_version.txt b/linter_version.txt new file mode 100644 index 0000000..f2db485 --- /dev/null +++ b/linter_version.txt @@ -0,0 +1,12 @@ +golangci-lint: v1.63.4 +revive: v1.5.1 +gosec: v2.22.0 +errcheck: v1.8.0 +unconvert: latest +goimports: v0.29.0 +gci: v0.13.5 +gofumpt: v0.7.0 +staticcheck: v0.5.1 +swag: v1.16.4 +go-enum: v0.6.0 +govulncheck: latest \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..7817b1c --- /dev/null +++ b/main.go @@ -0,0 +1,201 @@ +// package qfixpt is a Micro service quantex base project +package main + +import ( + "embed" + "flag" + "fmt" + "log/slog" + "os" + "time" + + "github.com/mitchellh/panicwrap" + + "quantex.com/qfixpt/src/app" + "quantex.com/qfixpt/src/app/mode" + "quantex.com/qfixpt/src/app/version" + "quantex.com/qfixpt/src/client/config" + googlechat "quantex.com/qfixpt/src/client/notify/google" + "quantex.com/qfixpt/src/client/res" + "quantex.com/qfixpt/src/cmd" + "quantex.com/qfixpt/src/cmd/example" + "quantex.com/qfixpt/src/cmd/service" + "quantex.com/qfixpt/src/common/logger" + "quantex.com/qfixpt/src/common/logger/tint" + "quantex.com/qfixpt/src/domain" +) + +// Embed the entire directory. + +//go:embed res +var resources embed.FS + +type flagsType struct { + runner, globalCfg, serviceCfg, logLevel, logFormat string + debug, version bool +} + +func main() { + flags := parseFlags() + + if len(os.Args) == 1 { + flag.Usage() + + return + } + + if flags.version { + fmt.Printf("%s version %s", version.AppName, version.Info()) + + return + } + + cfg, err := config.Read([]string{flags.globalCfg, flags.serviceCfg}) + if err != nil { + panic(fmt.Sprintf("Something went wrong reading the config. %s", err)) + } + + notify := googlechat.New(cfg.Notify.Google) + + exitStatus, err := panicwrap.BasicWrap(func(output string) { + notify.SendMsg(domain.MessageChannelPanic, output, domain.MessageStatusStopper, nil) + os.Exit(1) + }) + if err != nil { + // Something went wrong setting up the panic wrapper. + panic(fmt.Sprintf("Something went wrong setting up the panic wrapper. %s", err)) + } + // If exitStatus >= 0, then we're the parent process and the panicwrap + // re-executed ourselves and completed. Just exit with the proper status. + if exitStatus >= 0 { + os.Exit(exitStatus) + } + + handler, err := newLogHandler(flags) + if err != nil { + panic(err) + } + + hook := logger.NewQuantexHandler(handler, []slog.Level{slog.LevelError, slog.LevelWarn}) + slog.SetDefault(slog.New(hook)) + + slog.Info("New Run ----------------------------------------------------------------------------") + + slog.Debug(fmt.Sprintf("flags: %+v", flags)) + + v := version.AppName + " version " + version.Info() + fmt.Println(v) + slog.Info(v) + + setDebugMode(flags.debug) + + startRunner(flags.runner, flags.globalCfg, flags.serviceCfg) +} + +func parseFlags() (f flagsType) { + fmt.Println(f.runner) + flag.StringVar(&f.runner, "run", "", "run the specified service") + + flag.StringVar(&f.globalCfg, "global-cfg", "../global_conf.toml", "set the config global file name") + + flag.StringVar(&f.serviceCfg, "service-cfg", "conf.toml", "set the config service file name") + + const u0 = "set the logs output format: text, json, tint1 (one line), tint2 (two lines)" + flag.StringVar(&f.logFormat, "logs-format", "json", u0) + flag.StringVar(&f.logFormat, "f", "json", u0+" (shorthand)") + + const u1 = "set the debug mode" + flag.BoolVar(&f.debug, "debug", false, u1) + flag.BoolVar(&f.debug, "d", false, u1+" (shorthand)") + + const u2 = "show the program version" + flag.BoolVar(&f.version, "version", false, u2) + flag.BoolVar(&f.version, "v", false, u2+" (shorthand)") + + const u3 = "set the log level: debug, info, warn, error" + flag.StringVar(&f.logLevel, "level", "info", u3) + flag.StringVar(&f.logLevel, "l", "info", u3+" (shorthand)") + + flag.Parse() + + return f +} + +func newLogHandler(flags flagsType) (slog.Handler, error) { + logLevel, err := parseLogLevel(flags.logLevel) + if err != nil { + return nil, err + } + + switch flags.logFormat { + case "json": + return logger.NewJSONHandler(logLevel), nil + case "text": + return logger.NewTextHandler(logLevel), nil + case "tint1": + return logger.NewTextHandlerTint(logLevel, tint.OneLine), nil + case "tint2": + return logger.NewTextHandlerTint(logLevel, tint.TwoLines), nil + default: + flag.PrintDefaults() + + return nil, fmt.Errorf("invalid log format option: \"%s\"", flags.logFormat) + } +} + +func setDebugMode(debug bool) { + mode.Debug = debug + + var msg string + + if mode.Debug { + res.Set(os.DirFS("./")) + + msg = fmt.Sprintf("Running %s in DEBUG mode!", version.AppName) + } else { + res.Set(resources) + + msg = fmt.Sprintf("Running %s!", version.AppName) + } + + slog.Info(msg) +} + +func parseLogLevel(level string) (slog.Level, error) { + switch level { + case "debug": + return slog.LevelDebug, nil + case "info": + return slog.LevelInfo, nil + case "warn": + return slog.LevelWarn, nil + case "error": + return slog.LevelError, nil + default: + return 0, fmt.Errorf("invalid log level: %s", level) + } +} + +func startRunner(runner, globalCfg, serviceCfg string) { + var fn func(cfg app.Config) error + switch runner { + case "service": + fn = service.Runner + case "async": + fn = example.AsyncRunner + case "external": + fn = example.ExternalRunner + case "tracerr": + fn = example.TracerrRunner + case "logger": + fn = example.LogsRunner + default: + panic("Invalid runner option: \"" + runner + "\"") + } + + if err := cmd.NewRunner(fn)(globalCfg, serviceCfg); err != nil { + slog.Error(err.Error()) + // Time guard to allow to notify to send the message + time.Sleep(2 * time.Second) + } +} diff --git a/res/file.txt b/res/file.txt new file mode 100644 index 0000000..b25666e --- /dev/null +++ b/res/file.txt @@ -0,0 +1 @@ +To Embed diff --git a/spec/FIX50SP2-DS-CORI.xml b/spec/FIX50SP2-DS-CORI.xml new file mode 100644 index 0000000..26fc549 --- /dev/null +++ b/spec/FIX50SP2-DS-CORI.xml @@ -0,0 +1,4751 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/app/mode/mode.go b/src/app/mode/mode.go new file mode 100644 index 0000000..9ce6adc --- /dev/null +++ b/src/app/mode/mode.go @@ -0,0 +1,5 @@ +// Package mode indicates application modes, it has variables to indicate special settings. +package mode + +// Debug indicates if debug mode is enabled, so for example we can log debug info. +var Debug bool //nolint diff --git a/src/app/model.go b/src/app/model.go new file mode 100644 index 0000000..1c437db --- /dev/null +++ b/src/app/model.go @@ -0,0 +1,122 @@ +// Package app defines all app models +package app + +import ( + "github.com/shopspring/decimal" + + "quantex.com.ar/multidb" + notifyall "quantex.com/qfixpt/src/client/notify/all" +) + +//revive:disable:max-public-structs // This is a file containing app public models + +type Config struct { + Global + Service +} + +type Global struct { + GitUser string + GitPass string + CertEncryptionKey string + Async Async `toml:"MQTT"` + MultiDB multidb.Config + AllowedOrigins []string + ExchangeRateAPIKey string + RatingStartDate string + ScreenshotFolder string + QApixPort string + QApixHost string // Optional + Notify notifyall.Config +} + +type Service struct { + QApixToken string // TODO find a better way to authenticate + External map[string]ExtAuth `toml:"External"` + AuthorizedServices map[string]AuthorizedService `toml:"AuthorizedServices"` + APIBasePort string + EnableJWTAuth bool // Enable JWT authentication for service-to-service communication +} + +type ExtAuth struct { + Host string + Port string + Name string + Token string +} + +type AuthorizedService struct { + Name string + Permissions []ServicePermission + Token *string +} + +func (s *AuthorizedService) HasPermissions(requiredPerm ServicePermission) bool { + for _, perm := range s.Permissions { + if perm == requiredPerm || perm == ServicePermissionFullAccess { + return true + } + } + + return false +} + +type Async struct { + Protocol string + URL string + Subdomain string + Secret string +} + +type TargetParty struct { + Party Entity + Favorite bool + Selected bool +} + +type Entity struct { + ID string + Name string +} + +type InstID string + +type User struct { + UserID string + Token string + Name string + LastName string + Password string + Email string + Phone string + Sender Entity + Party Entity + BannedParties []Entity + TargetParties []TargetParty + Subscriptions []InstID + IsMiddleman bool + IsSuperUser bool + IsBackOffice bool + IsTrader bool + IsService bool + IsPartyAdmin bool + IsViewer bool + IsTesting bool + AllowTokenAuth bool + Rating decimal.Decimal +} + +type UserDataProvider interface { + GetUserByEmail(email string) User +} + +//go:generate go-enum -f=$GOFILE --lower --marshal + +// Service Permissions +// ENUM( +// ReadOnly +// ReadWrite +// FullAccess +// Undefined +// ) +type ServicePermission int //nolint:recvcheck // The methods of this are autogenerated diff --git a/src/app/version/version.go b/src/app/version/version.go new file mode 100644 index 0000000..720a634 --- /dev/null +++ b/src/app/version/version.go @@ -0,0 +1,118 @@ +// Package version defines current build app information +package version + +import ( + "fmt" + "os" + "runtime" + "strings" +) + +//go:generate go-enum -f=$GOFILE --lower --marshal + +// AppName stores the Application Name +const ( + AppName = "qfixpt" + poweredBy = "Powered by Quantex Technologies" + quantexEnvironment = "QUANTEX_ENVIRONMENT" +) + +var ( + // See tools/build.sh script to see how these variables are set + versionBase = "0.1" //nolint:gochecknoglobals // Should be global cos this is set on the build with version info + buildHash string //nolint:gochecknoglobals // Idem + buildBranch string //nolint:gochecknoglobals // Idem + builtTime string //nolint:gochecknoglobals // Idem + hostname string //nolint:gochecknoglobals // Idem +) + +// EnvironmentType environments +// ENUM( +// prod +// open-demo +// demo +// dev +// ) +type EnvironmentType int //nolint:recvcheck // The methods of this are autogenerated + +var environment EnvironmentType //nolint:gochecknoglobals // Just keept this global to avoid having to create an instance + +func init() { + aux := os.Getenv(quantexEnvironment) + if aux == "" { + panic("QUANTEX_ENVIRONMENT is not set") + } + + env, err := ParseEnvironmentType(aux) + if err != nil { + panic("Invalid QUANTEX_ENVIRONMENT value: " + aux + " " + err.Error()) + } + + environment = env +} + +// Base returns the version base name +func Base() string { + return versionBase +} + +// BuildHash returns the build hash +func BuildHash() string { + return buildHash +} + +// BuildBranch returns the build branch +func BuildBranch() string { + return buildBranch +} + +// Hostname returns the build time +func Hostname() string { + return hostname +} + +// Name returns the version name +func Name() string { + bh := buildHash + if len(buildHash) >= 8 { + bh = buildHash[0:8] + } + + return versionBase + "-" + bh +} + +// BuiltTime returns the build time +func BuiltTime() string { + return builtTime +} + +// Environment returns service environment +func Environment() EnvironmentType { + return environment +} + +// Environment returns service environment +func BuildFullInfo() string { + bt := strings.Replace(builtTime, "-", " ", -1) + bh := buildHash + + if len(buildHash) >= 8 { + bh = buildHash[0:8] + } + + return fmt.Sprintf("v%s-%s-%s, built on %s ", versionBase, buildBranch, bh, bt) +} + +// Info returns the complete version information +func Info() string { + sb := strings.Builder{} + // revive:disable No need to handle errors here + sb.WriteString(fmt.Sprintf("v%s \n", versionBase)) + sb.WriteString(fmt.Sprintln(" Build ", BuildFullInfo())) + sb.WriteString(fmt.Sprintln(" ", poweredBy, "")) + sb.WriteString(fmt.Sprintf(" Go %s \n", runtime.Version())) + sb.WriteString(fmt.Sprintf(" Environment %s \n", environment)) + sb.WriteString(fmt.Sprintf(" Built from %s \n", hostname)) + // revive:enable + return sb.String() +} diff --git a/src/client/api/async/model.go b/src/client/api/async/model.go new file mode 100644 index 0000000..d816311 --- /dev/null +++ b/src/client/api/async/model.go @@ -0,0 +1,21 @@ +// Package async defines functions to assist application with parallel jobs +package async + +//go:generate go-enum -f=$GOFILE --lower --marshal + +import ( + "time" +) + +//nolint:varcheck // This is ok to keep here for future use. Remove this comment when start using qos constant. +const ( + qos = 2 // Quality of Service. 2 -> Only once + retryInterval = time.Second * 5 +) + +// Origin specify where is the server located +// ENUM( +// Local +// Server +// ) +type Origin int //nolint:recvcheck // The methods of this are autogenerated diff --git a/src/client/api/async/mqtt_client.go b/src/client/api/async/mqtt_client.go new file mode 100644 index 0000000..68cdb2f --- /dev/null +++ b/src/client/api/async/mqtt_client.go @@ -0,0 +1,186 @@ +package async + +import ( + "encoding/json" + "fmt" + blog "log" + "log/slog" + "os" + "path" + "strconv" + "time" + + mqtt "github.com/eclipse/paho.mqtt.golang" + "github.com/golang-jwt/jwt/v5" + "quantex.com/qfixpt/src/app" + "quantex.com/qfixpt/src/app/version" + "quantex.com/qfixpt/src/common/tracerr" + "quantex.com/qfixpt/src/domain" +) + +const defaultTokenExpireSeconds = 60 + +type MQTTManager struct { + client mqtt.Client + config app.Async + notify domain.Notifier +} + +func New(cfg app.Async, n domain.Notifier) *MQTTManager { + manager := &MQTTManager{ + config: cfg, + notify: n, + } + + manager.client = manager.newClient(version.AppName+strconv.FormatInt(time.Now().Unix(), 10), nil) + + return manager +} + +func (m *MQTTManager) Start() { + token := m.client.Connect() + if token.Wait() && token.Error() != nil { + slog.Error("Error trying to connect to broker = " + token.Error().Error()) + + return + } + + slog.Info("MQTT Client successfully created!") +} + +//nolint:ireturn,nolintlint // We don't control this +func (m *MQTTManager) newClient(clientID string, onConnectHandler mqtt.OnConnectHandler) mqtt.Client { + mqtt.ERROR = blog.New(os.Stdout, "", 0) + clientID = clientID + "_" + strconv.Itoa(time.Now().Nanosecond()) + opts := mqtt.NewClientOptions() + + err := checkMQTTConfig(m.config) + if err != nil { + panic(err.Error()) + } + + mqttBkr := fmt.Sprintf("%s://%s", m.config.Protocol, m.config.URL) + + opts.AddBroker(mqttBkr).SetClientID(clientID) + opts.SetAutoReconnect(true) + opts.SetKeepAlive(5 * time.Second) + opts.SetPingTimeout(2 * time.Second) + opts.SetMaxReconnectInterval(2 * time.Second) + opts.SetDefaultPublishHandler(msgHandler) + opts.SetCredentialsProvider(m.credentialHandler) + opts.OnConnectionLost = m.connectionLostHandler + + if onConnectHandler != nil { + opts.SetOnConnectHandler(onConnectHandler) + } + + return mqtt.NewClient(opts) +} + +func (m *MQTTManager) credentialHandler() (username, password string) { + token, err := generateMqttToken(version.AppName, m.config.Secret, defaultTokenExpireSeconds) + if err != nil { + msg := tracerr.Errorf("Error getting token = %w", err) + slog.Error(msg.Error()) + + m.notify.SendMsg(domain.MessageChannelError, msg.Error(), domain.MessageStatusStopper, nil) + + return "", "" + } + + return version.AppName, token +} + +func (m *MQTTManager) Subscribe(topic string, handler func(topic string, msg []byte)) { + t := path.Join(m.config.Subdomain, "quantex/", topic) + token := m.client.Subscribe(t, qos, func(_ mqtt.Client, msg mqtt.Message) { + handler(msg.Topic(), msg.Payload()) + }) + m.CheckSubscribe(token, t) +} + +func (m *MQTTManager) Publish(topic string, msg any) { + payload, err := json.Marshal(msg) + if err != nil { + slog.Error("error. could not send alert msg: " + err.Error()) + + return + } + + // Publish a message + t := path.Join(m.config.Subdomain, "quantex/", topic) + token := m.client.Publish(t, qos, false, payload) + m.CheckPublish(token, t, payload) +} + +func (m *MQTTManager) connectionLostHandler(client mqtt.Client, reason error) { + opts := client.OptionsReader() + msg := fmt.Sprintf("MQTT Connection lost for client: %s. Reason: %s", opts.ClientID(), reason.Error()) + slog.Warn(msg) + m.notify.SendMsg(domain.MessageChannelError, msg, domain.MessageStatusWarning, nil) +} + +func msgHandler(_ mqtt.Client, msg mqtt.Message) { + slog.Info(fmt.Sprintf("Message received: [mqtt] -> [A] | received: '%s' topic: '%s'", + msg.Payload(), msg.Topic())) +} + +func generateMqttToken(user, secret string, exp int64) (string, error) { + // Create a new token object, specifying signing method and the claims + // you would like it to contain. + now := time.Now().Unix() + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "username": user, + "exp": now + exp, + "iat": now, + }) + + // Sign and get the complete encoded token as a string using the secret + tokenString, err := token.SignedString([]byte(secret)) + if err != nil { + e := tracerr.Errorf("error generating the MQTT token %w", err) + + return "", e + } + + return tokenString, nil +} + +// CheckPublish check the result of publish action. +// For asynchronous check use: +// go CheckPublish(token, topic, payload) +func (m *MQTTManager) CheckPublish(token mqtt.Token, topic string, payload []byte) { + slog.Info("[mqtt: %s] <- %s\n", topic, string(payload)) + + errMsg := tracerr.Errorf("MQTT Publish Error. Topic: %s. Error: %w", topic, token.Error()) + m.checkToken(token, errMsg) +} + +// CheckSubscribe check the result of subscribe action. +// For asynchronous check use: +// go CheckSubscribe(token, topic) +func (m *MQTTManager) CheckSubscribe(token mqtt.Token, topic string) { + slog.Info("subscribing to [mqtt: " + topic + "]") + + errMsg := tracerr.Errorf("MQTT Subscriber Error. Topic: %s. Error: %w", topic, token.Error()) + m.checkToken(token, errMsg) +} + +func (m *MQTTManager) checkToken(token mqtt.Token, errMsg error) { + if token.Wait() && token.Error() != nil { + err := tracerr.Errorf("checkToken error: %w", errMsg) + slog.Error(err.Error()) + m.notify.SendMsg(domain.MessageChannelError, err.Error(), domain.MessageStatusWarning, nil) + } +} + +func checkMQTTConfig(config app.Async) error { + if config.Protocol == "" || + config.URL == "" || + config.Subdomain == "" || + config.Secret == "" { + return tracerr.Errorf("mqtt configuration is needed: Protocol, URL, Subdomain and/or Secret are empty") + } + + return nil +} diff --git a/src/client/api/rest/controller.go b/src/client/api/rest/controller.go new file mode 100644 index 0000000..ae605ea --- /dev/null +++ b/src/client/api/rest/controller.go @@ -0,0 +1,290 @@ +package rest + +import ( + "fmt" + "log/slog" + "net/http" + "time" + + "github.com/gin-gonic/gin" + "github.com/gomodule/redigo/redis" + "github.com/sasha-s/go-deadlock" + uuid "github.com/satori/go.uuid" + + "quantex.com/qfixpt/src/app" + "quantex.com/qfixpt/src/app/version" + "quantex.com/qfixpt/src/client/store" + "quantex.com/qfixpt/src/common/tracerr" + "quantex.com/qfixpt/src/domain" +) + +const ( + ProdEnv = "prod" + TokenExpireTime = 1200 * time.Second +) + +const ( + responseKey = "responseKey" + sessionTokenKey = "sessionTokenKey" +) + +type Controller struct { + pool *redis.Pool + userData app.UserDataProvider + store *store.Store + config Config + notify domain.Notifier + authMutex deadlock.Mutex +} + +func newController(pool *redis.Pool, userData app.UserDataProvider, + s *store.Store, config Config, n domain.Notifier, +) *Controller { + return &Controller{ + pool: pool, + userData: userData, + store: s, + config: config, + notify: n, + } +} + +func (cont *Controller) GetUser(ctx *gin.Context) app.User { + // This is set on the AuthRequired middleware + response, ok := ctx.Get(responseKey) + if !ok { + // TODO log this issue + ctx.JSON(http.StatusInternalServerError, HTTPError{Error: "internal server error"}) + } + + val, ok := response.([]uint8) + if !ok { + // TODO log this issue + ctx.JSON(http.StatusInternalServerError, HTTPError{Error: "internal server error"}) + } + + return cont.userData.GetUserByEmail(string(val)) +} + +// Login godoc +// @Summary Login +// @Description Authenticate a User using credentials +// @Tags auth +// @Accept json +// @Produce json +// @Param credentials body Credentials true "Authentication" +// @Success 200 {object} Session +// @Failure 400 {object} HTTPError +// @Failure 401 {object} HTTPError +// @Failure 404 {object} HTTPError +// @Failure 500 {object} HTTPError +// @Router /auth/login [post] +func (cont *Controller) Login(ctx *gin.Context) { + defer cont.authMutex.Unlock() + cont.authMutex.Lock() + + setHeaders(ctx, cont.config) + + // Get the JSON body and decode into credentials + var creds Credentials + if err := ctx.ShouldBindJSON(&creds); err != nil { + ctx.JSON(http.StatusBadRequest, HTTPError{Error: err.Error()}) + + return + } + + // Get the expected Password from our in memory map + + expectedUser := cont.userData.GetUserByEmail(creds.Email) + + // If a Password exists for the given User + // AND, if it is the same as the Password we received, the we can move ahead + // if NOT, then we return an "Unauthorized" status + if expectedUser.Email == "" || expectedUser.Password != creds.Password { + ctx.JSON(http.StatusUnauthorized, HTTPError{Error: "Invalid credentials"}) + + return + } + + // Create a new random session token + sessionToken := uuid.NewV4().String() + + // Set the token in the cache, along with the User whom it represents + // The token has an expiry time of 120 seconds + conn := cont.pool.Get() + defer func() { + err := conn.Close() + if err != nil { + e := tracerr.Errorf("error closing connection: %w", err) + slog.Error(e.Error()) + } + }() + + _, err := conn.Do("SETEX", sessionToken, TokenExpireTime, creds.Email) + if err != nil { + slog.Error(tracerr.Errorf("Error setting token in redis cache: %w", err).Error()) + // If there is an error in setting the cache, return an internal server error + ctx.JSON(http.StatusInternalServerError, HTTPError{Error: err.Error()}) + + return + } + + // Finally, we set the client cookie for "session_token" as the session token we just generated + // we also set an expiry time of 120 seconds, the same as the cache + + // Finally, we set the client cookie for "session_token" as the session token we just generated + // we also set an expiry time of TokenExpireTime, the same as the cache + cookie := &http.Cookie{ + Name: "session_token", + Value: sessionToken, + HttpOnly: true, + Path: "/", + Secure: true, + Expires: time.Now().Add(TokenExpireTime), + } + + if version.Environment() == version.EnvironmentTypeDev { + cookie.SameSite = http.SameSiteNoneMode + } + + http.SetCookie(ctx.Writer, cookie) + + ctx.JSON(http.StatusOK, Session{Email: creds.Email}) +} + +// Refresh godoc +// @Summary Refresh the authorization token +// @Description This endpoint must be called periodically to get a new token before the current one expires +// @Tags auth +// @Accept json +// @Produce json +// @Success 200 {object} Msg +// @Failure 400 {object} HTTPError +// @Failure 401 {object} HTTPError +// @Failure 404 {object} HTTPError +// @Failure 500 {object} HTTPError +// @Router /auth/refresh [get] +func (cont *Controller) Refresh(ctx *gin.Context) { + defer cont.authMutex.Unlock() + cont.authMutex.Lock() + + setHeaders(ctx, cont.config) + + // (BEGIN) The code until this point is the same as the first part of the `Welcome` route + response, ok1 := ctx.Get(responseKey) + if !ok1 { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) + } + // (END) The code uptil this point is the same as the first part of the `Welcome` route + + // Now, create a new session token for the current User + newSessionToken := uuid.NewV4().String() + + conn := cont.pool.Get() + defer func() { + err := conn.Close() + if err != nil { + e := tracerr.Errorf("error closing connection: %w", err) + slog.Error(e.Error()) + } + }() + + _, err := conn.Do("SETEX", newSessionToken, "120", fmt.Sprintf("%s", response)) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + + return + } + + // Set the new token as the Users `session_token` cookie + cookie := &http.Cookie{ + Name: "session_token", + Value: newSessionToken, + HttpOnly: true, + Path: "/", + Secure: true, + Expires: time.Now().Add(TokenExpireTime), + } + + if version.Environment() == version.EnvironmentTypeDev { + cookie.SameSite = http.SameSiteNoneMode + } + + http.SetCookie(ctx.Writer, cookie) + + ctx.JSON(http.StatusOK, Msg{Text: "Token updated"}) +} + +// HealthCheck godoc +// @Summary Health check +// @Description Return service health +// @Tags health +// @Produce json +// @Success 200 {object} map[string]string +// @Router /health [get] +func (cont *Controller) HealthCheck(ctx *gin.Context) { + // ensure CORS and other headers are set consistently + setHeaders(ctx, cont.config) + + status := struct { + Status string `json:"status"` + Build string `json:"build"` + Sha string `json:"sha"` + JwtAuthentications string `json:"jwtAuthentications,omitempty"` + }{ + Status: "ok", + Build: version.BuildBranch(), + Sha: version.BuildHash(), + } + + // Only check JWT authentication if enabled + if cont.config.EnableJWTAuth { + status.JwtAuthentications = "ok" + + user, err := cont.store.UserByEmail("fede") + if err != nil || user == nil { + + status.JwtAuthentications = "error" + status.Status = "degraded" + + err = tracerr.Errorf("error fetching user: %w", err) + + slog.Error(err.Error()) + + ctx.JSON(http.StatusInternalServerError, status) + + return + } + } + + // return a minimal JSON health response + ctx.JSON(http.StatusOK, status) +} + +// revive:disable:cyclomatic // We need this complexity +func setHeaders(ctx *gin.Context, config Config) { + origin := ctx.Request.Header.Get("Origin") + + if allowed(origin, config) { + ctx.Writer.Header().Set("Access-Control-Allow-Origin", origin) + ctx.Writer.Header().Set("Access-Control-Allow-Credentials", "true") + ctx.Writer.Header().Set("Access-Control-Allow-Headers", + "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin,"+ + "UserCache-Control, X-Requested-With") + ctx.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT") + } +} + +func allowed(origin string, config Config) bool { + if version.Environment() == version.EnvironmentTypeProd { + return origin == "https://monitor.quantex.com.ar" + } + + for _, o := range config.AllowedOrigins { + if o == origin { + return true + } + } + + return false +} diff --git a/src/client/api/rest/midlewares.go b/src/client/api/rest/midlewares.go new file mode 100644 index 0000000..e003ed4 --- /dev/null +++ b/src/client/api/rest/midlewares.go @@ -0,0 +1,254 @@ +package rest + +import ( + "errors" + "net/http" + "strings" + + "github.com/gin-gonic/gin" + "github.com/rs/zerolog/log" + + "quantex.com/qfixpt/src/app" + jwttoken "quantex.com/qfixpt/src/common/jwttoken" + "quantex.com/qfixpt/src/common/tracerr" +) + +const ErrorField = "error" + +func (cont *Controller) PartyAdmin(c *gin.Context) { + if cont.GetUser(c).IsPartyAdmin { + return + } + + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized Admin"}) +} + +func (cont *Controller) CanSendOrder(c *gin.Context) { + if cont.GetUser(c).IsViewer { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Rol Viewer is unauthorized to send orders"}) + } +} + +func (cont *Controller) Middleman(c *gin.Context) { + if cont.GetUser(c).IsMiddleman { + return + } + + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized Middleman"}) +} + +func (cont *Controller) BackOfficeUser(c *gin.Context) { + if cont.GetUser(c).IsBackOffice { + return + } + + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized User"}) +} + +func (cont *Controller) SuperUser(c *gin.Context) { + if cont.GetUser(c).IsSuperUser { + return + } + + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized Admin User"}) +} + +func (cont *Controller) Options(c *gin.Context) { + if c.Request.Method != http.MethodOptions { + c.Next() + } else { + setHeaders(c, cont.config) + c.AbortWithStatus(http.StatusOK) + } +} + +func (cont *Controller) AuthRequired(ctx *gin.Context) { + setHeaders(ctx, cont.config) + + if c, err := ctx.Cookie("session_token"); c != "" && err == nil { + cont.SessionCookieAuth(ctx) + + return + } + + // check header for Token Auth + reqToken := ctx.GetHeader("Authorization") + if reqToken != "" { + cont.AuthorizationAuth(reqToken, ctx) + + return + } + + log.Error().Msg("Token Auth Unauthorized: missing session cookie and Authorization header") + + ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized - AuthRequired 1"}) +} + +func (cont *Controller) AuthorizationAuth(reqToken string, ctx *gin.Context) { + token := strings.TrimSpace(strings.TrimPrefix(reqToken, "Bearer")) + + if cont.config.EnableJWTAuth && jwttoken.IsJWT(token) { + cont.JWTTokenAuth(token, ctx) + + return + } + + cont.BearerTokenAuth(token, ctx) +} + +func (cont *Controller) BearerTokenAuth(reqToken string, ctx *gin.Context) { + if !strings.HasPrefix(reqToken, "Bearer ") { + log.Error().Msg("Token Auth Unauthorized: missing Bearer prefix") + + ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Token Auth Unauthorized"}) + + return + } + + token := strings.Split(reqToken, "Bearer ") + if len(token) != 2 { + + log.Error().Msg("Token Auth Unauthorized at TokenAuth: invalid token format") + + ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Token Auth Unauthorized 1"}) + + return + } + + user, err := cont.validateUserToken(token[1]) + if err != nil { + err = errors.New("Token Auth Unauthorized at TokenAuth - %s" + err.Error()) + + log.Error().Msg(err.Error()) + + ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Token Auth Unauthorized 2"}) + + return + } + + log.Info().Msgf("User %s authenticated successfully at TokenAuth", user.Email) + + ctx.Set(responseKey, []byte(user.Email)) + + ctx.Next() +} + +func (cont *Controller) JWTTokenAuth(token string, ctx *gin.Context) { + from := ctx.Query("from") + + serviceAuth, err := jwttoken.Validate(from, token, cont.config.AuthorizedServices) + if err != nil || serviceAuth == nil || serviceAuth.Token == nil { + err := tracerr.Errorf("invalid token or claims: %w", err) + + log.Error().Msg(err.Error()) + + ctx.AbortWithStatusJSON(http.StatusUnauthorized, + gin.H{ErrorField: err.Error()}) + + return + } + + user, err := cont.validateUserToken(*serviceAuth.Token) + if err != nil { + err = tracerr.Errorf("Token Auth Unauthorized at JWTTokenAuth: %w", err) + + log.Error().Msg(err.Error()) + + ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Invalid credentials"}) + + return + } + + ctx.Set("authorized_service", serviceAuth) + ctx.Set(responseKey, []byte(user.Email)) // TODO: will services be treated as users? if not remove this line + + log.Info().Str("issuer", serviceAuth.Name).Msg("Service authenticated successfully") + + ctx.Next() +} + +func (cont *Controller) validateUserToken(token string) (user *app.User, err error) { + userInfo := strings.Split(token, ":") + if len(userInfo) != 2 || userInfo[1] == "" { + err = tracerr.Errorf("invalid token format at validateUserToken") + + log.Error().Msg(err.Error()) + + return nil, err + } + + email := userInfo[0] + + user, err = cont.store.UserByEmail(email) + if user == nil || err != nil { + err = tracerr.Errorf("user not found at validateUserToken: %w", err) + + log.Error().Msg(err.Error()) + + return nil, err + } + + tkn := userInfo[1] + + if user.Token != tkn { + err = tracerr.Errorf("invalid token credentials at validateUserToken") + + log.Error().Msg(err.Error()) + + return nil, err + } + + log.Info().Str("email", user.Email).Msg("Service user validated successfully") + + return user, nil +} + +func (cont *Controller) SessionCookieAuth(ctx *gin.Context) { + // We can obtain the session token from the requests cookies, which come with every handler + sessionToken, err := ctx.Cookie("session_token") + if err != nil { + if errors.Is(err, http.ErrNoCookie) { + // If the cookie is not set, return an unauthorized status + ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized - AuthRequired 1"}) + + return + } + // For any other type of error, return a bad handler status + ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{ErrorField: "Bad handler - AuthRequired 2"}) + + return + } + + cont.validateCookieToExternal(sessionToken, ctx) +} + +func (cont *Controller) validateCookieToExternal(sessionToken string, ctx *gin.Context) { + ok, err := cont.store.ValidateSession(sessionToken) + if err != nil { + ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized - AuthRequired 4"}) + + return + } + + if !ok { + // If the session token is not valid, return an unauthorized error + ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized - AuthRequired 5"}) + + return + } + + ctx.Next() +} + +func IPWhiteList(whitelist map[string]bool) gin.HandlerFunc { + return func(c *gin.Context) { + if !whitelist[c.ClientIP()] { + c.AbortWithStatusJSON(http.StatusForbidden, gin.H{ + "status": http.StatusForbidden, + "message": "Permission denied", + }) + + return + } + } +} diff --git a/src/client/api/rest/model.go b/src/client/api/rest/model.go new file mode 100644 index 0000000..26f90dd --- /dev/null +++ b/src/client/api/rest/model.go @@ -0,0 +1,18 @@ +package rest + +type HTTPError struct { + Error string +} + +type Msg struct { + Text string +} + +type Credentials struct { + Email string `json:"email" binding:"required" example:"user1"` + Password string `json:"password" binding:"required" example:"password1"` +} + +type Session struct { + Email string +} diff --git a/src/client/api/rest/routes.go b/src/client/api/rest/routes.go new file mode 100644 index 0000000..444e17f --- /dev/null +++ b/src/client/api/rest/routes.go @@ -0,0 +1,39 @@ +package rest + +import ( + "github.com/gin-gonic/gin" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" + + _ "quantex.com/qfixpt/src/client/api/rest/docs" // Swag needs this import to work properly +) + +func SetRoutes(api *API) { + cont := api.Controller + + v1 := api.Router.Group("/qfixpt/v1") + api.Router.Use(cont.Options) + { + auth := v1.Group("/auth") + auth.POST("/login", cont.Login) + } + + qfixpt := v1.Group("/") + qfixpt.Use(cont.AuthRequired) + qfixpt.GET("/health", cont.HealthCheck) + + backoffice := qfixpt.Group("/backoffice") + backoffice.Use(cont.BackOfficeUser) + + admin := qfixpt.Group("/admin") + admin.Use(cont.SuperUser) + + SetSwagger(v1, cont) +} + +func SetSwagger(path *gin.RouterGroup, cont *Controller) { + auth := path.Group("/") + auth.Use(cont.AuthRequired) + + auth.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) +} diff --git a/src/client/api/rest/server.go b/src/client/api/rest/server.go new file mode 100644 index 0000000..41fa1de --- /dev/null +++ b/src/client/api/rest/server.go @@ -0,0 +1,95 @@ +// Package rest defines all API rest functionality +package rest + +import ( + "log/slog" + "time" + + "github.com/gin-gonic/gin" + "github.com/gomodule/redigo/redis" + + "quantex.com/qfixpt/src/app" + "quantex.com/qfixpt/src/app/version" + "quantex.com/qfixpt/src/client/store" + "quantex.com/qfixpt/src/common/logger" + "quantex.com/qfixpt/src/common/tracerr" + "quantex.com/qfixpt/src/domain" +) + +const RedisMaxIdle = 3000 // In ms + +type API struct { + Router *gin.Engine + Controller *Controller + Port string +} + +type Config struct { + AllowedOrigins []string + External map[string]app.ExtAuth `toml:"External"` + AuthorizedServices map[string]app.AuthorizedService `toml:"AuthorizedServices"` + Port string + EnableJWTAuth bool +} + +func New(userData app.UserDataProvider, storeInstance *store.Store, config Config, notify domain.Notifier) *API { + // Set up Gin + var engine *gin.Engine + if version.Environment() == version.EnvironmentTypeProd { + gin.SetMode(gin.ReleaseMode) + engine = gin.New() + engine.Use(gin.Recovery()) + // Use a custom logger middleware + engine.Use(logger.GinLoggerMiddleware(slog.Default())) + } else { + gin.SetMode(gin.DebugMode) + engine = gin.New() + // Don't use recovery middleware in debug mode + engine.Use(gin.Logger()) + } + + err := engine.SetTrustedProxies([]string{"127.0.0.1"}) + if err != nil { + panic("error setting trusted proxies: %v" + err.Error()) + } + + if config.Port == "" { + panic("API Base Port can not be empty!") + } + + api := &API{ + Controller: newController(NewPool(), userData, storeInstance, config, notify), + Router: engine, + Port: config.Port, + } + + SetRoutes(api) + + return api +} + +func NewPool() *redis.Pool { + return &redis.Pool{ + MaxIdle: RedisMaxIdle, + IdleTimeout: 1 * time.Second, + Dial: func() (redis.Conn, error) { + c, err := redis.DialURL("redis://localhost") + if err != nil { + return nil, tracerr.Errorf("error connecting to Redis: %w", err) + } + + return c, nil + }, + } +} + +// Run starts the API +func (api *API) Run() { + // Gin blocks the calling gorutine, so we start it in its own gorutine + // calling directly go api.Router.Run doesn't prevent having to press double + // ctrl+c to stop the service + go func() { + // start the server + slog.Error(api.Router.Run("localhost:" + api.Port).Error()) + }() +} diff --git a/src/client/api/rest/traslator.go b/src/client/api/rest/traslator.go new file mode 100644 index 0000000..0062e0c --- /dev/null +++ b/src/client/api/rest/traslator.go @@ -0,0 +1 @@ +package rest diff --git a/src/client/api/rest/validator.go b/src/client/api/rest/validator.go new file mode 100644 index 0000000..0062e0c --- /dev/null +++ b/src/client/api/rest/validator.go @@ -0,0 +1 @@ +package rest diff --git a/src/client/config/config.go b/src/client/config/config.go new file mode 100644 index 0000000..bb6a5d9 --- /dev/null +++ b/src/client/config/config.go @@ -0,0 +1,31 @@ +// Package config defines all application configuration +package config + +import ( + "os" + "path/filepath" + + "github.com/BurntSushi/toml" + + "quantex.com/qfixpt/src/app" + "quantex.com/qfixpt/src/common/tracerr" +) + +// Read the config from disk +func Read(files []string) (cfg app.Config, err error) { + for _, file := range files { + var d []byte + + if file != "" { + if d, err = os.ReadFile(filepath.Clean(file)); err != nil { + return app.Config{}, tracerr.Errorf("%v", err.Error()) + } + } + + if _, err = toml.Decode(string(d), &cfg); err != nil { + return app.Config{}, tracerr.Errorf("%v", err.Error()) + } + } + + return cfg, nil +} diff --git a/src/client/data/data.go b/src/client/data/data.go new file mode 100644 index 0000000..b0d384c --- /dev/null +++ b/src/client/data/data.go @@ -0,0 +1,16 @@ +// Package data defines data functions as cache +package data + +import ( + "quantex.com/qfixpt/src/app" +) + +type Data struct{} + +func New() *Data { + return &Data{} +} + +func (*Data) GetUserByEmail(string) app.User { + return app.User{} +} diff --git a/src/client/notify/all/all.go b/src/client/notify/all/all.go new file mode 100644 index 0000000..537a618 --- /dev/null +++ b/src/client/notify/all/all.go @@ -0,0 +1,32 @@ +// Package notifyall provides functionality for sending messages to multiple chat platforms +package notifyall + +import ( + "sync" + + google "quantex.com/qfixpt/src/client/notify/google" + "quantex.com/qfixpt/src/client/notify/slack" + "quantex.com/qfixpt/src/domain" +) + +type AllNotify struct { + google *google.Notify + slack *slack.Notify +} + +type Config struct { + Slack domain.Channels + Google domain.Channels +} + +func New(cfg Config) *AllNotify { + return &AllNotify{ + google: google.New(cfg.Google), + slack: slack.New(cfg.Slack), + } +} + +func (an AllNotify) SendMsg(chat domain.MessageChannel, text string, status domain.MessageStatus, wg *sync.WaitGroup) { + an.google.SendMsg(chat, text, status, wg) + an.slack.SendMsg(chat, text, status, wg) +} diff --git a/src/client/notify/common.go b/src/client/notify/common.go new file mode 100644 index 0000000..0b0e760 --- /dev/null +++ b/src/client/notify/common.go @@ -0,0 +1,142 @@ +// Package notify provides utilities to the notify packages +package notify + +import ( + "bytes" + "fmt" + "io" + "log/slog" + "net/http" + "sync" + "time" + + "quantex.com/qfixpt/src/common/logger" + "quantex.com/qfixpt/src/common/tracerr" + "quantex.com/qfixpt/src/domain" +) + +// maxRetry is the send message maximum number of tries. +const maxRetry = 5 + +// waitBetweenTries is the time in seconds that the SendCo func should wait before try again. +const waitBetweenTries = 5 + +//revive:disable:argument-limit // need this length to pass the logger +func SendWithRetry(text string, status domain.MessageStatus, waitgroup *sync.WaitGroup, url string, + getMessage func(string, domain.MessageStatus) string, +) { + loc, err := time.LoadLocation("America/Argentina/Buenos_Aires") + if err != nil { + logger.ErrorWoNotifier("error loading timezone %v", err) + + return + } + + // TODO check why we have these, here it should be a better way to avoid this + start := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 7, 30, 0, 0, loc) + + if time.Now().Before(start) { + slog.Info("skipping notification, inactive app") + + return + } + + for range maxRetry { + err = send(text, status, url, getMessage) + if err == nil { + slog.Info("msg sent with notifier: " + text) + + if waitgroup != nil { + waitgroup.Done() + } + + return + } + + time.Sleep(time.Second * waitBetweenTries) + } + + logger.ErrorWoNotifier("unable to send msg in %d tries", maxRetry) + + if waitgroup != nil { + waitgroup.Done() + } +} + +//revive:enable:argument-limit + +func send(text string, status domain.MessageStatus, url string, + getMessage func(string, domain.MessageStatus) string, +) (errOut error) { + slog.Debug("URL:> " + url) + + msg := getMessage(text, status) // this function changes + + slog.Debug(msg) + + jsonStr := []byte(msg) + + req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(jsonStr)) //nolint:noctx // we don't need context here + if err != nil { + logger.ErrorWoNotifier("%v", err.Error()) + + return err //nolint:wrapcheck // we don't need to wrap this error + } + + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{ + Timeout: time.Second * 10, + } + + resp, err := client.Do(req) + if err != nil { + return err //nolint:wrapcheck // we don't need to wrap this error + } + + if resp == nil { + err = tracerr.Errorf("error at send: '%s' response is nil", req.URL) + + logger.ErrorWoNotifier("%v", err.Error()) + + return err + } + + defer func() { + if err := resp.Body.Close(); err != nil { + err = tracerr.Errorf("error closing body request while sending msg at send: %v", err) + + logger.ErrorWoNotifier("%v", err.Error()) + } + }() + + r := resp.Status + slog.Debug("response Status: " + r) + slog.Debug(fmt.Sprintf("response Headers: %+v", resp.Header)) + + body, err := io.ReadAll(resp.Body) + if err != nil { + logger.ErrorWoNotifier("error reading response body while sending msg, error: %s", err.Error()) + } + + slog.Debug("response Body: " + string(body)) + + if resp.StatusCode != http.StatusOK { + err = tracerr.Errorf("error sending msg statusCode: %d body: %s", resp.StatusCode, string(body)) + + logger.ErrorWoNotifier("%v", err.Error()) + + return err + } + + return nil +} + +func ValidateChannelConfig(channels domain.Channels) error { + if channels.Test == "" || channels.Web == "" || channels.Panic == "" || channels.Error == "" { + return tracerr.Errorf("channels configuration is needed: Test, Web, Panic and/or Error are empty. "+ + "cfg: %v", channels) + } + + return nil +} diff --git a/src/client/notify/google/google_chat.go b/src/client/notify/google/google_chat.go new file mode 100644 index 0000000..61b6186 --- /dev/null +++ b/src/client/notify/google/google_chat.go @@ -0,0 +1,271 @@ +// Package googlechat provides functionality to send google chat notifications +package googlechat + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "net" + "os" + "strings" + "sync" + "time" + + "github.com/sasha-s/go-deadlock" + + "quantex.com/qfixpt/src/app/version" + common "quantex.com/qfixpt/src/client/notify" + "quantex.com/qfixpt/src/common/logger" + "quantex.com/qfixpt/src/common/tracerr" + "quantex.com/qfixpt/src/domain" +) + +//revive:disable:line-length-limit It's just links + +type SpaceID string + +// spaces URLs +const googleChatURL = "https://chat.googleapis.com/v1/spaces/" + +type Notify struct { + spaces map[domain.MessageChannel]SpaceID + messages map[string]time.Time + m deadlock.Mutex +} + +//nolint:lll // It's just a link +func New(chls domain.Channels) *Notify { + if err := common.ValidateChannelConfig(chls); err != nil { + panic(tracerr.Errorf("google config error: %w", err)) + } + + messages := make(map[string]time.Time) + spaces := make(map[domain.MessageChannel]SpaceID) + + spaces[domain.MessageChannelTest] = SpaceID(chls.Test) + spaces[domain.MessageChannelWeb] = SpaceID(chls.Web) + spaces[domain.MessageChannelPanic] = SpaceID(chls.Panic) + spaces[domain.MessageChannelError] = SpaceID(chls.Error) + + return &Notify{ + spaces: spaces, + messages: messages, + } +} + +func (g *Notify) Example() { + wg := sync.WaitGroup{} + wg.Add(3) + + go g.SendMsg(domain.MessageChannelTest, "Hello World!", domain.MessageStatusGood, &wg) + go g.SendMsg(domain.MessageChannelTest, "Error", domain.MessageStatusStopper, &wg) + go g.SendMsg(domain.MessageChannelTest, "Warning", domain.MessageStatusWarning, &wg) + + wg.Wait() +} + +func (g *Notify) SendMsg(chat domain.MessageChannel, text string, status domain.MessageStatus, waitgroup *sync.WaitGroup) { + var space SpaceID + + if s, ok := g.spaces[chat]; !ok { + space = g.spaces[domain.MessageChannelError] + + err := tracerr.Errorf("error sending google notification, there's no space id for chat: %s", chat) + + logger.ErrorWoNotifier("%v", err.Error()) + } else { + space = s + } + + s := fmt.Sprintf("%s-%s-%s", text, status.String(), string(space)) + + if ok := g.shouldSendMessage(s); ok { + go common.SendWithRetry(text, status, waitgroup, googleChatURL+string(space), getMessage) + } +} + +//nolint:lll // It's just a link +const ( + errImg = "https://media.gettyimages.com/id/1359003186/es/foto/illustration-of-a-tick-or-an-x-indicating-right-and-wrong.jpg?s=612x612&w=0&k=20&c=FdYIXI1qCPpVcY6phcIJom8sRhZw4hgYVtwOC6lNlmA=" + goodImg = "https://media.gettyimages.com/id/1352723074/es/foto/drawing-of-green-tick-check-mark.jpg?s=612x612&w=0&k=20&c=RgoCJ-n0OpZSClCWhJstoXAfiGrBZhnggUvOp2ooJqI=" + warnImg = "https://media.gettyimages.com/id/1407160246/es/vector/icono-de-tri%C3%A1ngulo-de-peligro.jpg?s=612x612&w=0&k=20&c=rI0IYmZmkg62txtNQFhyTbHx5oW311_OupBkbWODfjg=" +) + +func getImage(status domain.MessageStatus) (string, error) { + switch status { + case domain.MessageStatusGood: + return goodImg, nil + case domain.MessageStatusWarning: + return warnImg, nil + case domain.MessageStatusStopper: + return errImg, nil + } + + return "", tracerr.Errorf("unknown Message Status Type") +} + +func getMessage(text string, status domain.MessageStatus) string { + image, err := getImage(status) + if err != nil { + err = tracerr.Errorf("error getting image at getMessage, error: %w", err) + + logger.ErrorWoNotifier("%v", err.Error()) + + var e error + + image, e = getImage(domain.MessageStatusWarning) + if e != nil { + e = tracerr.Errorf("error getting image at getMessage, error: %w", e) + + logger.ErrorWoNotifier("%v", e.Error()) + } + } + + env := version.Environment() + appVersion := version.Base() + + text = strings.ReplaceAll(text, "'", "\"") + + msg := ` + { + 'cardsV2': [{ + 'cardId': 'createCardMessage', + 'card': { + 'header': { + 'title': 'qfixpt', + 'subtitle': 'Notification', + 'imageUrl': '%s', + 'imageType': 'CIRCLE' + }, + 'sections': [ + { + 'widgets':[ + { + 'textParagraph': { + 'text': 'Environment: %s' + } + }, + { + 'textParagraph': { + 'text': 'Message: %s' + } + }, + { + 'textParagraph': { + 'text': 'Build: %s' + } + }, + { + 'textParagraph': { + 'text': 'Time: %s' + } + }, + { + 'textParagraph': { + 'text': 'Hostname: %s' + } + }, + { + 'textParagraph': { + 'text': 'IP: %s' + } + } + ] + } + ] + } + }] + }` + + loc, err := time.LoadLocation("America/Argentina/Buenos_Aires") + if err != nil { + err := tracerr.Errorf("error loading timezone %v, setting default UTC", err) + + logger.ErrorWoNotifier("%v", err.Error()) + + loc = time.UTC + } + + now := time.Now().In(loc).Format("15:04:05 02-01-2006 -0700") + jsonMsg := fmt.Sprintf(msg, image, env, text, appVersion, now, + getHostname(), getOutboundIP()) + + return jsonMsg +} + +func (g *Notify) shouldSendMessage(s string) (ok bool) { + sha, err := generateShaString(s) + if err != nil { + err = tracerr.Errorf("error generating sha string at shouldSendMessage, error: %w", err) + + logger.ErrorWoNotifier("%v", err.Error()) + + return true + } + + g.m.Lock() + defer g.m.Unlock() + + if t, ok := g.messages[sha]; ok && time.Since(t) < time.Minute { + return false + } + + g.messages[sha] = time.Now() + + return true +} + +func generateShaString(s string) (sha string, err error) { + hash := sha256.New() + if _, err := hash.Write([]byte(s)); err != nil { + err = tracerr.Errorf("error generating hash at generateShaString, error: %w", err) + logger.ErrorWoNotifier("%v", err.Error()) + + return sha, err + } + + return hex.EncodeToString(hash.Sum(nil)), nil +} + +//revive:enable:line-length-limit + +//nolint:gochecknoglobals // need to be global +var outboundIP string + +func getOutboundIP() string { + if outboundIP == "" { + conn, err := net.Dial("udp", "8.8.8.8:80") + if err != nil { + logger.ErrorWoNotifier("failed to get UDP address") + + return "" + } + + defer func(conn net.Conn) { + err = conn.Close() + if err != nil { + logger.ErrorWoNotifier("error closing net connection") + } + }(conn) + + localAddr, ok := conn.LocalAddr().(*net.UDPAddr) + if !ok { + logger.ErrorWoNotifier("error: conn.LocalAddr() is not a *net.UDPAddr type") + } + + outboundIP = localAddr.IP.String() + } + + return outboundIP +} + +func getHostname() string { + hostname, err := os.Hostname() + if err != nil { + logger.ErrorWoNotifier("failed to get hostname") + + return "" + } + + return hostname +} diff --git a/src/client/notify/slack/slack.go b/src/client/notify/slack/slack.go new file mode 100644 index 0000000..a3c2d4c --- /dev/null +++ b/src/client/notify/slack/slack.go @@ -0,0 +1,122 @@ +// Package slack provides functionality to send slack notifications +package slack + +import ( + "fmt" + "sync" + "time" + + "quantex.com/qfixpt/src/app/version" + common "quantex.com/qfixpt/src/client/notify" + "quantex.com/qfixpt/src/common/logger" + "quantex.com/qfixpt/src/common/tracerr" + "quantex.com/qfixpt/src/domain" +) + +type ChannelKey string + +const slackBotURL = "https://hooks.slack.com/services/" + +type Notify struct { + channels map[domain.MessageChannel]ChannelKey +} + +func New(chls domain.Channels) *Notify { + if err := common.ValidateChannelConfig(chls); err != nil { + panic(tracerr.Errorf("slack config error: %w", err)) + } + + channels := make(map[domain.MessageChannel]ChannelKey) + + channels[domain.MessageChannelTest] = ChannelKey(chls.Test) + channels[domain.MessageChannelWeb] = ChannelKey(chls.Web) + channels[domain.MessageChannelPanic] = ChannelKey(chls.Panic) + channels[domain.MessageChannelError] = ChannelKey(chls.Error) + + return &Notify{ + channels: channels, + } +} + +func (s *Notify) Example() { + wg := sync.WaitGroup{} + wg.Add(1) + s.SendMsg(domain.MessageChannelTest, "Hello World!", domain.MessageStatusGood, &wg) + wg.Wait() +} + +func (s *Notify) SendMsg(channel domain.MessageChannel, text string, status domain.MessageStatus, waitgroup *sync.WaitGroup) { + var chKey ChannelKey + + if c, ok := s.channels[channel]; !ok { + chKey = s.channels[domain.MessageChannelError] + + err := tracerr.Errorf("error sending slack notification, there's no channel key for channel: %s", channel) + + logger.ErrorWoNotifier("%v", err.Error()) + } else { + chKey = c + } + + common.SendWithRetry(text, status, waitgroup, slackBotURL+string(chKey), getMessage) +} + +func getStatusTypeString(status domain.MessageStatus) (str string, err error) { + switch status { + case domain.MessageStatusGood: + return "good", nil + case domain.MessageStatusWarning: + return "warning", nil + case domain.MessageStatusStopper: + return "danger", nil + } + + return "", tracerr.Errorf("unknown Message Status Type") +} + +func getMessage(text string, status domain.MessageStatus) string { + dt := time.Now() + dtf := dt.Format("15:04:05.000 -07 [02/01/2006]") + + sts, err := getStatusTypeString(status) + if err != nil { + err := tracerr.Errorf("error getting status type string: %v", err) + + logger.ErrorWoNotifier("%v", err.Error()) + + var e error + + sts, e = getStatusTypeString(domain.MessageStatusWarning) + if e != nil { + e = tracerr.Errorf("error getting status type string: %v", e) + + logger.ErrorWoNotifier("%v", e.Error()) + } + } + + appVersion := version.Base() + + msg := ` + { + "username": "SystemMonitor", + "attachments": [ + { + "color": "%s", + "title": "qfixpt", + "title_link": "https://api.slack.com/", + "text": "%s\n%s", + "fields": [ + { + "title": "Build", + "value": "%s", + "short": false + } + ] + } + ] + }` + + jsonMsg := fmt.Sprintf(msg, sts, text, dtf, appVersion) + + return jsonMsg +} diff --git a/src/client/res/resources.go b/src/client/res/resources.go new file mode 100644 index 0000000..2e55eda --- /dev/null +++ b/src/client/res/resources.go @@ -0,0 +1,118 @@ +// Package res defines all resources +package res + +import ( + "embed" + "errors" + "io" + "io/fs" + "log/slog" + "net/http" + + "quantex.com/qfixpt/src/common/tracerr" +) + +var resources fs.FS //nolint + +func Set(r fs.FS) { + resources = r +} + +func Get() fs.FS { + return resources +} + +// ReadFile reads the named file and returns the contents. +// Got it from Go Standard Library: /usr/local/go/src/os/file.go +// A successful call returns err == nil, not err == EOF. +// Because ReadFile reads the whole file, it does not treat an EOF from Read +// as an error to be reported. +func ReadFile(name string) ([]byte, error) { + switch assets := resources.(type) { + case embed.FS: + b, err := assets.ReadFile(name) + if err != nil { + return nil, tracerr.Errorf("error reading file for embed.FS: %w", err) + } + + return b, nil + case fs.FS: + b, err := readFileInternal(name) + if err != nil { + return nil, tracerr.Errorf("error reading file for embed.FS: %w", err) + } + + return b, nil + default: + return nil, tracerr.Errorf("not allowed FS type: %T", assets) + } +} + +//revive:disable:cognitive-complexity we need this level of complexity +//revive:disable:cyclomatic we need this level of complexity +func readFileInternal(name string) ([]byte, error) { + file, err := resources.Open(name) + if err != nil { + return nil, tracerr.Errorf("error opening file %s: %w", name, err) + } + + defer func() { + e := file.Close() + if err != nil || e != nil { + err = tracerr.Errorf("error closing file %s: %w", e, err) + } + }() + + var size int + + if info, err := file.Stat(); err == nil { + size64 := info.Size() + if int64(int(size64)) == size64 { + size = int(size64) + } + } + + size++ // one byte for final read at EOF + + // If a file claims a small size, read at least 512 bytes. + // In particular, files in Linux's /proc claim size 0 but + // then do not work right if read in small pieces, + // so an initial read of 1 byte would not work correctly. + if size < 512 { + size = 512 + } + + data := make([]byte, 0, size) + + for { + if len(data) >= cap(data) { + d := append(data[:cap(data)], 0) + data = d[:len(data)] + } + + n, err := file.Read(data[len(data):cap(data)]) + data = data[:len(data)+n] + + if err != nil { + if errors.Is(err, io.EOF) { + err = nil + } + + return data, err + } + } +} + +//revive:enable:cyclomatic +//revive:enable:cognitive-complexity + +func GetFileSystem(prefix string) http.FileSystem { + fsys, err := fs.Sub(resources, prefix) + if err != nil { + e := tracerr.Errorf("error with prefix %s: %w", prefix, err) + + slog.Error(e.Error()) + } + + return http.FS(fsys) +} diff --git a/src/client/store/external/auth.go b/src/client/store/external/auth.go new file mode 100644 index 0000000..f4d9ced --- /dev/null +++ b/src/client/store/external/auth.go @@ -0,0 +1,42 @@ +package external + +import ( + "errors" + "fmt" + "log/slog" + "net/http" + "time" +) + +func (s *Manager) ValidateSession(sessionToken string) (bool, error) { + path := "/api/v1/auth/session/validate" + + auth, ok := s.config.External[QApixService] + if !ok { + err := errors.New("error validating auth to qapix service at ValidateSession") + slog.Error(err.Error()) + + return false, err + } + + options := RequestOptions{ + Method: http.MethodGet, + Path: path, + Body: nil, + Retries: 3, + Timeout: sTimeout * time.Second, + CacheDuration: time.Second * 10, + Auth: &auth, + SessionToken: sessionToken, + } + + res, err := s.sendRequestToExternal(options) + if err != nil || res == nil { + err := fmt.Errorf("error making ValidateSession request to qapix server. error: %w", err) + slog.Error(err.Error()) + + return false, err + } + + return true, nil +} diff --git a/src/client/store/external/manager.go b/src/client/store/external/manager.go new file mode 100644 index 0000000..6a298e1 --- /dev/null +++ b/src/client/store/external/manager.go @@ -0,0 +1,273 @@ +// Package external defines all external services access +package external + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "log/slog" + "net/http" + "strings" + "sync" + "time" + + "github.com/rs/zerolog/log" + "github.com/sasha-s/go-deadlock" + + "quantex.com/qfixpt/src/app" + "quantex.com/qfixpt/src/app/version" + jwttoken "quantex.com/qfixpt/src/common/jwttoken" + "quantex.com/qfixpt/src/domain" +) + +const ( + sTimeout = 10 + lTimeout = 15 +) + +type Config struct { + QApixPort string + QApixHost string + External map[string]app.ExtAuth + QApixToken string + EnableJWTAuth bool +} + +type cacheItem struct { + Time time.Time + Resp []byte +} + +type Manager struct { + config Config + notifier domain.Notifier + cache map[string]cacheItem + mutex deadlock.RWMutex +} + +type RequestOptions struct { + Method string + Path string + URL string + Token string + Secured bool + Body interface{} + Retries int + Timeout time.Duration + CacheDuration time.Duration + Auth *app.ExtAuth + SessionToken string +} + +// NewManager create new Manager struct +func NewManager(n domain.Notifier, cfg Config) *Manager { + return &Manager{ + config: cfg, + notifier: n, + cache: make(map[string]cacheItem), + } +} + +//revive:disable:argument-limit we need this arguments +func (s *Manager) sendRequestToExternal(opts RequestOptions) ([]byte, error) { + host := fmt.Sprintf("http://localhost:%v", opts.Auth.Port) + if opts.Auth.Host != "" { + host = opts.Auth.Host + } + + token := s.config.QApixToken + + secured := false + + if s.config.EnableJWTAuth && opts.Auth != nil { + t, err := jwttoken.Encrypt(*opts.Auth) + if err != nil { + e := fmt.Errorf("error encrypting quantexService: %w", err) + log.Error().Msg(e.Error()) + + return nil, e + } + + token = t + + secured = true + } + + opts.Secured = secured + opts.Token = token + opts.URL = urlFrom(host, opts.Path) + + return s.sendRequestWithCache(opts) +} + +func (s *Manager) sendRequestWithCache(opts RequestOptions) ([]byte, error) { + sha := optionSha(opts) + + if opts.CacheDuration > 0 && len(sha) > 0 { + s.mutex.RLock() + t, ok := s.cache[sha] + s.mutex.RUnlock() + + if ok && time.Since(t.Time) < opts.CacheDuration { + return t.Resp, nil + } + } + + resp, err := s.sendRequestWithRetries(opts) + if err == nil && len(sha) > 0 { + s.mutex.Lock() + s.cache[sha] = cacheItem{ + time.Now(), + resp, + } + s.mutex.Unlock() + } + + return resp, err +} + +//revive:disable:flag-parameter we need this flag +//nolint:funlen //it's long but easy to read +func (s *Manager) sendRequestWithRetries(opts RequestOptions) ([]byte, error) { + client := &http.Client{ + Timeout: opts.Timeout, + } + + bodyBytes, err := json.Marshal(&opts.Body) + if err != nil { + e := fmt.Errorf("error encoding the body: %v, %w", opts.Body, err) + slog.Error(e.Error()) + + return nil, e + } + + slog.Debug("sending request to: " + opts.URL) + + request, err := http.NewRequest(opts.Method, opts.URL, bytes.NewBuffer(bodyBytes)) //nolint:noctx //no ctx needed + if err != nil { + e := fmt.Errorf("error creating new %s request to: %s, %w", opts.Method, opts.URL, err) + slog.Error(e.Error()) + + return nil, e + } + + authorization := "Bearer " + opts.Token + if opts.Secured { + authorization = opts.Token + } + + request.Header.Set("Authorization", authorization) + + request.Header.Set("Content-Type", "application/json") + + if opts.SessionToken != "" { + // Create a cookie + cookie := &http.Cookie{ + Name: "session_token", + Value: opts.SessionToken, + Path: "/", + } + + // Add the cookie to the request + request.AddCookie(cookie) + } + + var resp *http.Response + for i := 0; i <= opts.Retries; i++ { //nolint:wsl // It's ok in this case + interval := time.Duration(i) * time.Second + slog.Debug(fmt.Sprintf("request to '%s' try #%v in %v", request.URL, i, interval)) + time.Sleep(interval) + + resp, err = client.Do(request) + if err != nil { + e := fmt.Errorf("error making request to %s. error: %w", request.URL, err) + slog.Error(e.Error()) + + // send notification if notifier is available + s.sendNotification(domain.MessageChannelError, e.Error(), domain.MessageStatusWarning, nil) + + continue + } + + break + } + + if resp == nil { + err = fmt.Errorf("error: '%s' response is nil", request.URL) + slog.Error(err.Error()) + + return nil, err + } + + defer func() { + if err := resp.Body.Close(); err != nil { + slog.Error("error closing response body at sendRequestWithRetries: " + err.Error()) + } + }() + + if resp.StatusCode != http.StatusOK { + msg := fmt.Sprintf("response code from %s is not 200. StatusCode: %d.", opts.URL, resp.StatusCode) + // send notification if notifier is available + s.sendNotification(domain.MessageChannelError, msg, domain.MessageStatusWarning, nil) + + return nil, fmt.Errorf("error %s", msg) + } + + bodyBytes, resErr := io.ReadAll(resp.Body) + if resErr != nil { + msg := fmt.Sprintf("error reading response from %s. error: %s", opts.URL, resErr.Error()) + // send notification if notifier is available + s.sendNotification(domain.MessageChannelError, msg, domain.MessageStatusWarning, nil) + + return nil, fmt.Errorf("error %s", msg) + } + + return bodyBytes, nil +} + +//revive:enable + +// sendNotification is a nil-safe wrapper around the notifier's SendMsg method. +// Some call sites run in contexts where Manager.notifier may be nil, so guard the call +// to avoid runtime panics. +func (s *Manager) sendNotification(channel domain.MessageChannel, message string, status domain.MessageStatus, wg *sync.WaitGroup) { + if s == nil || s.notifier == nil { + slog.Debug("notifier is nil, skipping SendMsg") + + return + } + + s.notifier.SendMsg(channel, message, status, wg) +} + +func optionSha(opts RequestOptions) string { + key := fmt.Sprintf("%s-%s-%s", opts.Method, opts.URL, opts.Body) + + return shaString(key) +} + +func shaString(s string) (sha string) { + hash := sha256.New() + if _, err := hash.Write([]byte(s)); err != nil { + err = fmt.Errorf("error generating hash at generateShaString, error: %w", err) + slog.Error(err.Error()) + + return "" + } + + return hex.EncodeToString(hash.Sum(nil)) +} + +func urlFrom(host, path string) string { + from := strings.ToLower(version.AppName) + + url := fmt.Sprintf("%s%s?from=%s", host, path, from) + if strings.Contains(path, "?") { + url = fmt.Sprintf("%s%s&from=%s", host, path, from) + } + + return url +} diff --git a/src/client/store/external/user.go b/src/client/store/external/user.go new file mode 100644 index 0000000..a6aa7b4 --- /dev/null +++ b/src/client/store/external/user.go @@ -0,0 +1,141 @@ +package external + +import ( + "encoding/json" + "errors" + "fmt" + "log/slog" + "net/http" + "time" + + "quantex.com/qfixpt/src/app" +) + +const QApixService = "QApix" + +func (s *Manager) Users(out any) (err error) { + auth, ok := s.config.External[QApixService] + if !ok { + err = errors.New("error getting auth data for qapix service at Users") + + slog.Error(err.Error()) + + return err + } + + options := RequestOptions{ + Method: http.MethodGet, + Path: "/api/v1/auth/data/users", + Body: nil, + Retries: 0, + Timeout: lTimeout * time.Second, + CacheDuration: -1, + Auth: &auth, + } + + response, err := s.sendRequestToExternal(options) + if err != nil { + err = fmt.Errorf("error making request to qapix server. error: %w", err) + + slog.Error(err.Error()) + + return err + } + + err = json.Unmarshal(response, out) + if err != nil { + err = fmt.Errorf("error making request to qapix server. error: %w", err) + + slog.Error(err.Error()) + + return err + } + + return nil +} + +func (s *Manager) UserByID(userID string, out any) (err error) { + path := "/api/v1/auth/data/user_by_id/" + userID + + auth, ok := s.config.External[QApixService] + if !ok { + err = errors.New("error getting auth data for qapix service at UserByEmail") + + slog.Error(err.Error()) + + return err + } + + options := RequestOptions{ + Method: http.MethodGet, + Path: path, + Body: nil, + Retries: 3, + Timeout: sTimeout * time.Second, + CacheDuration: time.Second * 10, + Auth: &auth, + } + + res, err := s.sendRequestToExternal(options) + if err != nil { + err = fmt.Errorf("error making user_by_id request to qapix server. error: %w", err) + + slog.Error(err.Error()) + + return err + } + + err = json.Unmarshal(res, out) + if err != nil { + err = fmt.Errorf("error unmarshalling user by id from qapix server. error: %w", err) + + slog.Error(err.Error()) + + return err + } + + return nil +} + +func (s *Manager) UserByEmail(email string, out *app.User) (err error) { + path := "/api/v1/auth/data/user_by_email" + + auth, ok := s.config.External[QApixService] + if !ok { + err = errors.New("error getting auth data for qapix service at UserByEmail") + + slog.Error(err.Error()) + + return err + } + + options := RequestOptions{ + Method: http.MethodPost, + Path: path, + Body: map[string]string{"Email": email}, + Retries: 3, + Timeout: sTimeout * time.Second, + CacheDuration: time.Second * 10, + Auth: &auth, + } + + res, err := s.sendRequestToExternal(options) + if err != nil { + err = fmt.Errorf("error making user_by_email request to qapix server. error: %w", err) + + slog.Error(err.Error()) + + return err + } + + err = json.Unmarshal(res, out) + if err != nil { + err = fmt.Errorf("error unmarshalling user by email from qapix server. error: %w", err) + + slog.Error(err.Error()) + + return err + } + + return nil +} diff --git a/src/client/store/manager.go b/src/client/store/manager.go new file mode 100644 index 0000000..73d172f --- /dev/null +++ b/src/client/store/manager.go @@ -0,0 +1,72 @@ +// Package store defines database functions +package store + +import ( + "log/slog" + "time" + + "quantex.com.ar/multidb" + "quantex.com/qfixpt/src/app" + "quantex.com/qfixpt/src/client/store/external" + "quantex.com/qfixpt/src/common/tracerr" +) + +const dbPingSeconds = 30 + +type Store struct { + db *multidb.MultiDB + ext *external.Manager +} + +type Config struct { + MultiDB multidb.Config + External external.Config +} + +// New NewStore creates Store object +func New(config Config) (*Store, error) { + database, err := multidb.New("postgres", config.MultiDB) + if err != nil { + return nil, tracerr.Errorf("error trying to create multidb: %w", err) + } + + database.Start() + + if err = database.Ping(); err != nil { + return nil, tracerr.Errorf("error ping to database: %w", err) + } + + ext := external.NewManager(nil, config.External) + + s := &Store{ + db: database, + ext: ext, + } + + go s.db.PeriodicDBPing(time.Second * dbPingSeconds) + + return s, nil +} + +func (p *Store) CloseDB() { + p.db.Close() + slog.Info("closing database connection.") +} + +func (p *Store) UserByEmail(email string) (out *app.User, err error) { + var user app.User + if err := p.ext.UserByEmail(email, &user); err != nil { + return nil, tracerr.Errorf("error fetching user by email from external service: %w", err) + } + + return &user, nil +} + +func (p *Store) ValidateSession(sessionToken string) (bool, error) { + ok, err := p.ext.ValidateSession(sessionToken) + if err != nil { + return false, tracerr.Errorf("error validating session: %w", err) + } + + return ok, nil +} diff --git a/src/cmd/base.go b/src/cmd/base.go new file mode 100644 index 0000000..e2c25d6 --- /dev/null +++ b/src/cmd/base.go @@ -0,0 +1,39 @@ +// Package cmd defines all runners +package cmd + +import ( + "os" + "os/signal" + "syscall" + + "quantex.com/qfixpt/src/app" + "quantex.com/qfixpt/src/client/config" + "quantex.com/qfixpt/src/common/tracerr" +) + +func NewRunner(fn func(app.Config) error) func(globalCfg, serviceCfg string) error { + return func(globalCfg, serviceCfg string) error { + cfg, err := config.Read([]string{globalCfg, serviceCfg}) + if err != nil { + return tracerr.Errorf("unable to read config running asyncRun: %s", err.Error()) + } + + err = fn(cfg) + if err != nil { + return tracerr.Errorf("error running traderRunner: %s", err.Error()) + } + + return nil + } +} + +func WaitForInterruptSignal(stopChan chan struct{}) { + interrupt := make(chan os.Signal, 10) + signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) + select { + case <-interrupt: + return + case <-stopChan: + return + } +} diff --git a/src/cmd/example/async.go b/src/cmd/example/async.go new file mode 100644 index 0000000..60a3648 --- /dev/null +++ b/src/cmd/example/async.go @@ -0,0 +1,83 @@ +// Package example contails some runners examples +package example + +import ( + "encoding/json" + "log/slog" + "time" + + mqtt "github.com/eclipse/paho.mqtt.golang" + + "quantex.com/qfixpt/src/app" + "quantex.com/qfixpt/src/client/api/async" + googlechat "quantex.com/qfixpt/src/client/notify/google" + "quantex.com/qfixpt/src/cmd" + "quantex.com/qfixpt/src/domain" +) + +func AsyncRunner(cfg app.Config) error { + slog.Info("Hello TryAsync") + + notify := googlechat.New(cfg.Notify.Google) + + asyncManager := async.New(cfg.Async, notify) + + asyncManager.Start() + + a := NewAux(asyncManager) + a.run() + + cmd.WaitForInterruptSignal(nil) + + return nil +} + +type Aux struct { + async domain.Asyncer +} + +func NewAux(a domain.Asyncer) *Aux { + return &Aux{ + async: a, + } +} + +type msgAux struct { + Message string + Count int64 + Time time.Time +} + +func (a *Aux) run() { + const luckyNumber = 13 + + msg := msgAux{ + Message: "Hello", + Count: luckyNumber, + Time: time.Now(), + } + + const topic = "qfixpt/try-async" + + a.async.Subscribe(topic, a.handler) + + a.async.Publish(topic, msg) +} + +func (*Aux) handler(topic string, msg []byte) { + var val msgAux + + err := json.Unmarshal(msg, &val) + if err != nil { + slog.Error(err.Error()) + } + + slog.Info(">>> Topic: %s, Msg: %+v", topic, val) +} + +//lint:ignore U1000 client will be implemented if necessary +func (a *Aux) onConnectHandler(_ mqtt.Client) { + const topic = "qfixpt/try-async" + + a.async.Subscribe(topic, a.handler) +} diff --git a/src/cmd/example/external.go b/src/cmd/example/external.go new file mode 100644 index 0000000..5c1d8d0 --- /dev/null +++ b/src/cmd/example/external.go @@ -0,0 +1,78 @@ +package example + +import ( + "fmt" + "log/slog" + + "quantex.com/qfixpt/src/app" + googlechat "quantex.com/qfixpt/src/client/notify/google" + "quantex.com/qfixpt/src/client/store/external" + "quantex.com/qfixpt/src/common/tracerr" + "quantex.com/qfixpt/src/domain" +) + +func ExternalRunner(cfg app.Config) error { + notify := googlechat.New(cfg.Notify.Google) + + slog.Info("Hello Try API") + + return tryExternal(notify, cfg) +} + +func tryExternal(notify *googlechat.Notify, cfg app.Config) error { + fmt.Println("\n @@@@@@@@@ Try External @@@@@@@@@ ") + defer fmt.Println("\n @@@@@@@@@ End Try External @@@@@@@@@ ") + + extConfig := external.Config{ + QApixPort: cfg.APIBasePort, + QApixHost: cfg.QApixHost, + External: cfg.External, + QApixToken: cfg.QApixToken, + } + + ext := external.NewManager(notify, extConfig) + + var users []app.User + + err := ext.Users(&users) + if err != nil { + return tracerr.Errorf("unable to read users: %w", err) + } + + slog.Info("Users count", "count", len(users)) + + if len(users) == 0 { + return tracerr.Errorf("error, users list is empty!") + } + + user := users[len(users)/2] + + var user1 domain.User + + err = ext.UserByID(user.UserID, &user1) + if err != nil { + return tracerr.Errorf("error UserByID %w", err) + } + + slog.Info(fmt.Sprintf("UserByID: %+v", user1)) + + var user2 app.User + + err = ext.UserByEmail(user.Email, &user2) + if err != nil { + return tracerr.Errorf("error UserByEmail %+v\n", err) + } + + slog.Info(fmt.Sprintf("UserByEmail: %+v", user2)) + + var badType []app.UserDataProvider + + err = ext.Users(&badType) + if err == nil { + return tracerr.Errorf("error users count: %v", len(badType)) + } + + slog.Info("Correct wrong type " + err.Error()) + + return nil +} diff --git a/src/cmd/example/logs.go b/src/cmd/example/logs.go new file mode 100644 index 0000000..d0260fd --- /dev/null +++ b/src/cmd/example/logs.go @@ -0,0 +1,50 @@ +package example + +import ( + "context" + "fmt" + "log/slog" + + "quantex.com/qfixpt/src/app" + googlechat "quantex.com/qfixpt/src/client/notify/google" + "quantex.com/qfixpt/src/cmd" + "quantex.com/qfixpt/src/common/logger" +) + +func LogsRunner(cfg app.Config) error { + notify := googlechat.New(cfg.Notify.Google) + logger.SetNotifier(notify) + + slog.Debug("This is a debug message with a tag", "tag", "debug") + slog.Info("This is an info message with a tag", "tag", "info") + slog.Error("This is an error message with a tag", "tag", "error") + + slog.Info("Press Ctrl-C to exit") + + fmt.Println("--------------------------------------------------") + + slog.Warn("This is a warn") + + slog.Info("Group example", + slog.Group("user", + slog.String("id", "1234"), + slog.String("name", "Juan PΓ©rez"), + ), + slog.String("action", "session start"), + ) + + type contextKey string + + const loggerKey contextKey = "logger" + + ctx := context.WithValue(context.Background(), loggerKey, nil) + + slog.InfoContext(ctx, "Log with context", + slog.String("user", "Juan PΓ©rez"), + slog.Int("id", 1234), + ) + + cmd.WaitForInterruptSignal(nil) + + return nil +} diff --git a/src/cmd/example/tracerr.go b/src/cmd/example/tracerr.go new file mode 100644 index 0000000..fb1ab87 --- /dev/null +++ b/src/cmd/example/tracerr.go @@ -0,0 +1,43 @@ +package example + +import ( + "errors" + "log/slog" + "time" + + "quantex.com/qfixpt/src/app" + googlechat "quantex.com/qfixpt/src/client/notify/google" + "quantex.com/qfixpt/src/client/res" + "quantex.com/qfixpt/src/common/logger" + "quantex.com/qfixpt/src/common/tracerr" +) + +func TracerrRunner(cfg app.Config) error { + slog.Info("Hello Tracerr") + + notify := googlechat.New(cfg.Notify.Google) + logger.SetNotifier(notify) + + readFile() + + errOrg := errors.New("--eror-- orig") + err := tracerr.Errorf("eror: %w", errOrg) + + slog.Info("Is the error??") + if errors.Is(err, errOrg) { + slog.Info("Yes, it's the error!") + } + + time.Sleep(2 * time.Second) + + return nil +} + +func readFile() { + f, err := res.ReadFile("res/file.txt") + if err != nil { + panic(err) + } + + slog.Info(">>> " + string(f)) +} diff --git a/src/cmd/service/service.go b/src/cmd/service/service.go new file mode 100644 index 0000000..6aef2cd --- /dev/null +++ b/src/cmd/service/service.go @@ -0,0 +1,55 @@ +// Package service contain the main service runner +package service + +import ( + "fmt" + "log/slog" + + "quantex.com/qfixpt/src/app" + "quantex.com/qfixpt/src/client/api/rest" + "quantex.com/qfixpt/src/client/data" + googlechat "quantex.com/qfixpt/src/client/notify/google" + "quantex.com/qfixpt/src/client/store" + "quantex.com/qfixpt/src/client/store/external" + "quantex.com/qfixpt/src/cmd" +) + +func Runner(cfg app.Config) error { + slog.Info("Hello Try Service") + + notify := googlechat.New(cfg.Notify.Google) + + extConfig := external.Config{ + QApixPort: cfg.APIBasePort, + QApixHost: cfg.QApixHost, + External: cfg.External, + QApixToken: cfg.QApixToken, + } + + storeConfig := store.Config{ + MultiDB: cfg.MultiDB, + External: extConfig, + } + + appStore, err := store.New(storeConfig) + if err != nil { + return fmt.Errorf("error trying to create store %w", err) + } + + userData := data.New() + + apiConfig := rest.Config{ + Port: cfg.APIBasePort, + AllowedOrigins: cfg.AllowedOrigins, + External: cfg.External, + AuthorizedServices: cfg.AuthorizedServices, + EnableJWTAuth: cfg.EnableJWTAuth, + } + + api := rest.New(userData, appStore, apiConfig, notify) + api.Run() + + cmd.WaitForInterruptSignal(nil) + + return nil +} diff --git a/src/common/jwttoken/jwt.go b/src/common/jwttoken/jwt.go new file mode 100644 index 0000000..e39d8e5 --- /dev/null +++ b/src/common/jwttoken/jwt.go @@ -0,0 +1,347 @@ +// Package jwttoken encrypts and decrypts tokens using JWT +package jwttoken + +import ( + "fmt" + "os" + "strings" + "sync" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/rs/zerolog" + zlog "github.com/rs/zerolog/log" + + "quantex.com/qfixpt/src/app" + "quantex.com/qfixpt/src/app/version" + "quantex.com/qfixpt/src/common/tracerr" +) + +const ( + postFix = "_QUANTEX_SECRET_KEY" + errAuthInvalidJWT = "invalid JWT token at %s" + errAuthMissingTokenClaim = "missing token claim %s at %s" + errAuthTokenNotSigned = "token could not be signed at Encrypt: %w" + errAuthMissingToken = "missing authentication token for service at %s: %s" + errAuthExpiredToken = "authentication token expired at %s" + errAuthExpClaim = "expiration claim is missing or invalid at %s" + errAuthSecretKeyNotFound = "secret key %s not found in environment" + errAuthTokenNotParsable = "token could not be parsed at %s:%w " + errAuthServiceNameEmpty = "service name cannot be empty at %s - received: %s" + claimToken = "token" + claimPermissions = "permissions" + claimIss = "iss" + claimExp = "exp" + claimIat = "iat" +) + +var claimsToValidate = []string{claimToken, claimIss} //nolint:gochecknoglobals // Set claims to validate + +var log zerolog.Logger //nolint + +var secretCache sync.Map //nolint:gochecknoglobals // Cache secrets after first lookup + +func init() { + log = zlog.With().Str("gtag", "secure_token").Logger().Level(zerolog.InfoLevel) +} + +// Validate decrypts and validates a JWT token and its claims. +// It returns the AuthorizedService associated with the token if valid. +// An error is returned if the token is invalid or any validation step fails. +// The service parameter specifies the expected issuer of the token. +func Validate(service, token string, authServices map[string]app.AuthorizedService) (*app.AuthorizedService, error) { + claims, err := decrypt(service, token) + if err != nil { + err := tracerr.Errorf("JWT Token could not be decrypted: %w", err) + + log.Error().Msg(err.Error()) + + return nil, err + } + + ok, err := validateJWTClaims(claims) + if err != nil || !ok { + err := tracerr.Errorf("invalid claims: %w", err) + + log.Error().Msg(err.Error()) + + return nil, err + } + + auth, err := serviceAuth(claims, authServices) + if err != nil { + err := tracerr.Errorf("service auth validation failed: %w", err) + + log.Error().Msg(err.Error()) + + return nil, err + } + + t, ok := claims[claimToken].(string) + if !ok { + err := tracerr.Errorf("token claim is not a string") + + log.Error().Msg(err.Error()) + + return nil, err + } + + auth.Token = &t + + return auth, nil +} + +// Encrypt creates a JWT token string from the provided ExtAuth information. +// It returns the signed JWT token string or an error if the process fails. +// The auth parameter contains the necessary information for token creation. +func Encrypt(auth app.ExtAuth) (string, error) { + if auth.Name == "" { + err := tracerr.Errorf("auth.Name cannot be empty at Encrypt") + + log.Error().Msg(err.Error()) + + return "", err + } + + claims := jwt.MapClaims{ + claimToken: auth.Token, + claimIss: version.AppName, + claimIat: time.Now().Unix(), + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + secret, err := findSecret(auth.Name) + if secret == nil || len(secret) == 0 || err != nil { + err := tracerr.Errorf(errAuthMissingToken, "Encrypt", auth.Name) + + log.Error().Msg(err.Error()) + + return "", err + } + + signedTkn, err := token.SignedString(secret) + if err != nil { + err = tracerr.Errorf(errAuthTokenNotSigned, err) + + log.Error().Msg(err.Error()) + + return "", err + } + + return signedTkn, nil +} + +// IsJWT checks if a token string is a valid JWT format. +// JWT tokens have exactly 3 parts separated by dots (header.payload.signature). +func IsJWT(token string) bool { + // Remove any whitespace + token = strings.TrimSpace(token) + + // JWT has exactly 3 parts separated by dots + parts := strings.Split(token, ".") + if len(parts) != 3 { + return false + } + + // Each part should be non-empty (base64url encoded) + for _, part := range parts { + if part == "" { + return false + } + } + + return true +} + +// decrypt decrypts a JWT token string using the secret associated with the given service. +// It returns the JWT claims if decryption is successful, or an error otherwise. +// The service parameter specifies the expected issuer of the token. +func decrypt(service, token string) (jwt.MapClaims, error) { + if service == "" { + err := tracerr.Errorf(errAuthServiceNameEmpty, "Decrypt", service) + + log.Error().Msg(err.Error()) + + return jwt.MapClaims{}, err + } + + secret, err := findSecret(service) + if secret == nil || len(secret) == 0 || err != nil { + err := tracerr.Errorf(errAuthMissingToken, "Decrypt", service) + + log.Error().Msg(err.Error()) + + return jwt.MapClaims{}, err + } + + tkn, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) { + if t.Method.Alg() != jwt.SigningMethodHS256.Alg() { + return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"]) + } + + return secret, nil + }) + if err != nil { + err = tracerr.Errorf(errAuthTokenNotParsable, "Decrypt", err) + + log.Error().Msg(err.Error()) + + return jwt.MapClaims{}, err + } + + claims, ok := tkn.Claims.(jwt.MapClaims) + if !ok { + err := tracerr.Errorf(errAuthInvalidJWT, "Decrypt") + + log.Error().Msg(err.Error()) + + return jwt.MapClaims{}, err + } + + return claims, nil +} + +// serviceAuth validates the service based on JWT claims and authorized services. +// It returns the AuthorizedService if validation is successful, or an error otherwise. +// The services parameter contains the map of authorized services to validate against. +func serviceAuth(claims jwt.MapClaims, services map[string]app.AuthorizedService) (out *app.AuthorizedService, err error) { + issuer, ok := claims[claimIss].(string) + if !ok { + err := tracerr.Errorf("issuer claim is not a string") + + log.Error().Msg(err.Error()) + + return nil, err + } + + service, ok := services[issuer] + if !ok { + err = tracerr.Errorf("Unknown service attempted access - issuer: %s", issuer) + + log.Warn().Str("issuer", issuer).Msg(err.Error()) + + return nil, err + } + + // TODO: change required permission as needed + ok = service.HasPermissions(app.ServicePermissionFullAccess) + if !ok { + err = tracerr.Errorf("Service without required permissions attempted access") + + log.Warn().Str("issuer", issuer).Msg(err.Error()) + + return nil, err + } + + return &service, nil +} + +// findSecret retrieves the secret key for a given application name from environment variables. +// It caches the secret after the first lookup for performance. +// It returns the secret as a byte slice or an error if not found. +func findSecret(appName string) ([]byte, error) { + key := strings.ToUpper(appName) + postFix + + if k, ok := secretCache.Load(key); ok { + if b, ok := k.([]byte); ok { + return b, nil + } + } + + secret := os.Getenv(key) + if secret == "" { + err := tracerr.Errorf(errAuthSecretKeyNotFound, key) + + return nil, err + } + + secretCache.Store(key, []byte(secret)) + + return []byte(secret), nil +} + +// validateJWTClaims checks the presence and validity of required JWT claims. +// It returns true if all claims are valid, or an error otherwise. +func validateJWTClaims(claims jwt.MapClaims) (ok bool, err error) { + for _, claim := range claimsToValidate { + ok, err = validateClaims(claims, claim) + if err != nil || !ok { + err := tracerr.Errorf("error %s token at ValidateClaims: %w", claim, err) + + log.Error().Msg(err.Error()) + + return false, err + } + } + + return true, nil +} + +// validateClaims validates a specific JWT claim based on its type. +func validateClaims(claims jwt.MapClaims, claim string) (ok bool, err error) { + switch claim { + case claimExp: + return validateExpiration(claims) + case claimToken: + return validateToken(claims) + case claimIss: + return validateIssuer(claims) + default: + err := fmt.Errorf("unknown claim %s at validateTokenClaims", claim) + + log.Error().Msg(err.Error()) + + return false, err + } +} + +// TODO: when needed add claimExp to claimsToValidate var to implement. +func validateExpiration(claims jwt.MapClaims) (ok bool, err error) { + exp, ok := claims[claimExp].(float64) + if !ok { + err := fmt.Errorf(errAuthExpClaim, "validateExpiration") + + log.Error().Msg(err.Error()) + + return false, nil + } + + if int64(exp) < time.Now().Unix() { + err := fmt.Errorf(errAuthExpiredToken, "validateExpiration") + + log.Error().Msg(err.Error()) + + return false, err + } + + return true, nil +} + +// validateToken checks for the presence of the token claim in the JWT claims. +func validateToken(claims jwt.MapClaims) (ok bool, err error) { + _, ok = claims[claimToken].(string) + if !ok { + err = tracerr.Errorf(errAuthMissingTokenClaim, claimToken, "validateToken") + + log.Error().Msg(err.Error()) + + return false, err + } + + return true, nil +} + +// validateIssuer checks for the presence of the issuer claim in the JWT claims. +func validateIssuer(claims jwt.MapClaims) (ok bool, err error) { + _, ok = claims[claimIss].(string) + if !ok { + err = tracerr.Errorf(errAuthMissingTokenClaim, claimIss, "validateIssuer") + + log.Error().Msg(err.Error()) + + return false, err + } + + return true, nil +} diff --git a/src/common/logger/handler.go b/src/common/logger/handler.go new file mode 100644 index 0000000..4910f39 --- /dev/null +++ b/src/common/logger/handler.go @@ -0,0 +1,85 @@ +package logger + +import ( + "context" + "fmt" + "log/slog" + "slices" + + "quantex.com/qfixpt/src/domain" +) + +//nolint:gochecknoglobals // need to be global +var notify domain.Notifier + +type QuantexHandler struct { + slog.Handler + levels []slog.Level +} + +func SetNotifier(n domain.Notifier) { + notify = n +} + +// NewQuantexHandler creates a log handler that send logs using a notifier. +func NewQuantexHandler(handler slog.Handler, levels []slog.Level) *QuantexHandler { + return &QuantexHandler{ + Handler: handler, + levels: levels, + } +} + +// Enabled reports whether the handler handles records at the given level. +func (s *QuantexHandler) Enabled(ctx context.Context, level slog.Level) bool { + return s.Handler.Enabled(ctx, level) +} + +// Handle intercepts and processes logger messages. +// In our case, send a message to the notifier. +// +//revive:disable:cognitive-complexity we need this level of complexity +func (s *QuantexHandler) Handle(ctx context.Context, record slog.Record) error { + const ( + shortErrKey = "err" + longErrKey = "error" + ) + + if notify != nil && slices.Contains(s.levels, record.Level) { + switch record.Level { + case slog.LevelError: + msg := record.Message + record.Attrs(func(attr slog.Attr) bool { + if attr.Key == shortErrKey || attr.Key == longErrKey { + if err, ok := attr.Value.Any().(error); ok { + msg += err.Error() + } + } + + return true + }) + + notify.SendMsg(domain.MessageChannelError, msg, domain.MessageStatusStopper, nil) + case slog.LevelWarn: + notify.SendMsg(domain.MessageChannelError, record.Message, domain.MessageStatusWarning, nil) + } + } + + err := s.Handler.Handle(ctx, record) + if err != nil { + return fmt.Errorf("error from QuantexHandler: %w", err) + } + + return nil +} + +//revive:enable + +// WithAttrs returns a new QuantexHandler whose attributes consists. +func (s *QuantexHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + return NewQuantexHandler(s.Handler.WithAttrs(attrs), s.levels) +} + +// WithGroup returns a new QuantexHandler whose group consists. +func (s *QuantexHandler) WithGroup(name string) slog.Handler { + return NewQuantexHandler(s.Handler.WithGroup(name), s.levels) +} diff --git a/src/common/logger/logger.go b/src/common/logger/logger.go new file mode 100644 index 0000000..e99890e --- /dev/null +++ b/src/common/logger/logger.go @@ -0,0 +1,137 @@ +// Package logger provides logging functionality for the application. +// It includes a Gin middleware for logging requests and responses. +package logger + +import ( + "context" + "fmt" + "log/slog" + "net/http" + "os" + "runtime" + "strings" + "time" + + "github.com/gin-gonic/gin" + + "quantex.com/qfixpt/src/app/version" + "quantex.com/qfixpt/src/common/logger/tint" +) + +//nolint:gochecknoglobals // need to be global +var loggerWoNotifier *slog.Logger + +func ErrorWoNotifier(msg string, args ...any) { + if loggerWoNotifier == nil || !loggerWoNotifier.Enabled(context.Background(), slog.LevelError) { + return + } + var pcs [1]uintptr + runtime.Callers(2, pcs[:]) // skip [Callers, Infof] + r := slog.NewRecord(time.Now(), slog.LevelError, fmt.Sprintf(msg, args...), pcs[0]) + err := loggerWoNotifier.Handler().Handle(context.Background(), r) + if err != nil { + // We can't use slog.Error here because it will try to send the message to the notifier + // and will create a recursive loop + slog.Info("error handling slog record in ErrorWoNotifier: " + err.Error()) + + return + } + + // loggerWoNotifier.Error(msg, args...) +} + +func NewTextHandler(level slog.Level) slog.Handler { + replace := func(_ []string, a slog.Attr) slog.Attr { + if a.Key == slog.SourceKey { + source, ok := a.Value.Any().(*slog.Source) + if !ok { + // handle the case when the assertion fails + slog.Warn(fmt.Sprintf("unexpected type %T for slog.Source", a.Value.Any())) + + return a + } + // Just add a space before the file path to enable to open the file from the IDE + p := strings.Split(source.File, strings.ToLower(version.AppName)+"/") + if len(p) > 1 { + source.File = " " + p[1] + } else { + source.File = " " + source.File + } + } + + if a.Key == slog.TimeKey { + a.Value = slog.StringValue(a.Value.Time().Format("15:04:05.000000")) + } + + return a + } + + opts := &slog.HandlerOptions{ + AddSource: true, + Level: level, + ReplaceAttr: replace, + } + + handler := slog.NewTextHandler(os.Stdout, opts) + loggerWoNotifier = slog.New(handler) + + return handler +} + +func NewTextHandlerTint(level slog.Level, lines tint.LinesCount) slog.Handler { + handler := tint.NewHandler(os.Stdout, &tint.Options{ + AddSource: true, + Level: level, + TimeFormat: "15:04:05.000000", + Lines: lines, + }) + + loggerWoNotifier = slog.New(handler) + + return handler +} + +func NewJSONHandler(level slog.Level) slog.Handler { + opts := &slog.HandlerOptions{ + AddSource: true, + Level: level, + } + + handler := slog.NewJSONHandler(os.Stdout, opts) + loggerWoNotifier = slog.New(handler) + + return handler +} + +// GinLoggerMiddleware creates a Gin middleware for logging using slog. +func GinLoggerMiddleware(logger *slog.Logger) gin.HandlerFunc { + return func(ctx *gin.Context) { + start := time.Now() + + // Store request parameters + params := []slog.Attr{ + slog.String("method", ctx.Request.Method), + slog.String("path", ctx.Request.URL.Path), + slog.Any("query", ctx.Request.URL.Query()), + slog.Any("headers", ctx.Request.Header), + } + + // Store the request body if needed + if ctx.Request.Method == http.MethodPost || ctx.Request.Method == http.MethodPut { + if body, err := ctx.GetRawData(); err == nil { + params = append(params, slog.String("body", string(body))) + } + } + + // Add status and duration to parameters + params = append(params, + slog.Int("status", ctx.Writer.Status()), + slog.String("duration", time.Since(start).String()), + ) + + // Log request details with slog + logger.LogAttrs(context.Background(), slog.LevelInfo, "Request Details", params...) + + ctx.Next() + } +} diff --git a/src/common/logger/tint/buffer.go b/src/common/logger/tint/buffer.go new file mode 100644 index 0000000..f416a32 --- /dev/null +++ b/src/common/logger/tint/buffer.go @@ -0,0 +1,56 @@ +package tint + +import "sync" + +type buffer []byte + +//nolint:gochecknoglobals // We are ok with this global definition +var bufPool = sync.Pool{ + New: func() any { + b := make(buffer, 0, 1024) + + return &b + }, +} + +func newBuffer() *buffer { + return bufPool.Get().(*buffer) //nolint:forcetypeassert // bufPool will always return a buffer +} + +func (b *buffer) Free() { + // To reduce peak allocation, return only smaller buffers to the pool. + const maxBufferSize = 16 << 10 + if cap(*b) <= maxBufferSize { + *b = (*b)[:0] + bufPool.Put(b) + } +} + +func (b *buffer) Write(bytes []byte) (int, error) { + *b = append(*b, bytes...) + + return len(bytes), nil +} + +func (b *buffer) WriteByte(char byte) error { + *b = append(*b, char) + + return nil +} + +func (b *buffer) WriteString(str string) (int, error) { + *b = append(*b, str...) + + return len(str), nil +} + +// revive:disable:flag-parameter in this case we consider is ok to use a flag as a parameter +func (b *buffer) WriteStringIf(ok bool, str string) (int, error) { + if !ok { + return 0, nil + } + + return b.WriteString(str) +} + +// revive:enable diff --git a/src/common/logger/tint/handler.go b/src/common/logger/tint/handler.go new file mode 100644 index 0000000..2575c48 --- /dev/null +++ b/src/common/logger/tint/handler.go @@ -0,0 +1,522 @@ +//nolint:dupword // This is documentation +/* +Package tint implements a zero-dependency [slog.Handler] that writes tinted +(colorized) logs. The output format is inspired by the [zerolog.ConsoleWriter] +and [slog.TextHandler]. + +The output format can be customized using [Options], which is a drop-in +replacement for [slog.HandlerOptions]. + +# Customize Attributes + +Options.ReplaceAttr can be used to alter or drop attributes. If set, it is +called on each non-group attribute before it is logged. +See [slog.HandlerOptions] for details. + +Create a new logger that doesn't write the time: + + w := os.Stderr + logger := slog.New( + tint.NewHandler(w, &tint.Options{ + ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { + if a.Key == slog.TimeKey && len(groups) == 0 { + return slog.Attr{} + } + return a + }, + }), + ) + +Create a new logger that writes all errors in red: + + w := os.Stderr + logger := slog.New( + tint.NewHandler(w, &tint.Options{ + ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { + if err, ok := a.Value.Any().(error); ok { + aErr := tint.Err(err) + aErr.Key = a.Key + return aErr + } + return a + }, + }), + ) + +# Automatically Enable Colors + +Colors are enabled by default and can be disabled using the Options.NoColor +attribute. To automatically enable colors based on the terminal capabilities, +use e.g. the [go-isatty] package. + + w := os.Stderr + logger := slog.New( + tint.NewHandler(w, &tint.Options{ + NoColor: !isatty.IsTerminal(w.Fd()), + }), + ) + +# Windows Support + +Color support on Windows can be added by using e.g. the [go-colorable] package. + + w := os.Stderr + logger := slog.New( + tint.NewHandler(colorable.NewColorable(w), nil), + ) + +[zerolog.ConsoleWriter]: https://pkg.go.dev/github.com/rs/zerolog#ConsoleWriter +[go-isatty]: https://pkg.go.dev/github.com/mattn/go-isatty +[go-colorable]: https://pkg.go.dev/github.com/mattn/go-colorable +*/ +package tint + +import ( + "context" + "encoding" + "errors" + "fmt" + "io" + "log/slog" + "runtime" + "strconv" + "strings" + "sync" + "time" + "unicode" + + "quantex.com/qfixpt/src/app/version" +) + +// ANSI modes +const ( + ansiReset = "\033[0m" + ansiFaint = "\033[2m" + ansiResetFaint = "\033[22m" + ansiBrightRed = "\033[91m" + ansiBrightGreen = "\033[92m" + ansiBrightYellow = "\033[93m" + ansiBrightRedFaint = "\033[91;2m" +) + +const ( + errKey = "err" + defaultLevel = slog.LevelInfo + defaultTimeFormat = time.StampMilli + OneLine = 1 + TwoLines = 2 +) + +type LinesCount int + +// Options for a slog.Handler that writes tinted logs. A zero Options consists +// entirely of default values. +// +// Options can be used as a drop-in replacement for [slog.HandlerOptions]. +type Options struct { + // Enable source code location (Default: false) + AddSource bool + + // Minimum level to log (Default: slog.LevelInfo) + Level slog.Leveler + + // ReplaceAttr is called to rewrite each non-group attribute before it is logged. + // See https://pkg.go.dev/log/slog#HandlerOptions for details. + ReplaceAttr func(groups []string, attr slog.Attr) slog.Attr + + // Time format (Default: time.StampMilli) + TimeFormat string + + // Disable color (Default: false) + NoColor bool + + Lines LinesCount +} + +// NewHandler creates a [slog.Handler] that writes tinted logs to Writer w, +// using the default options. If opts is nil, the default options are used. +func NewHandler(w io.Writer, opts *Options) slog.Handler { + h := &handler{ + w: w, + level: defaultLevel, + timeFormat: defaultTimeFormat, + } + if opts == nil { + return h + } + + h.addSource = opts.AddSource + if opts.Level != nil { + h.level = opts.Level + } + h.replaceAttr = opts.ReplaceAttr + if opts.TimeFormat != "" { + h.timeFormat = opts.TimeFormat + } + h.noColor = opts.NoColor + + if opts.Lines != OneLine && opts.Lines != TwoLines { + panic("invalid lines count " + strconv.Itoa(int(opts.Lines)) + ", must be OneLine or TwoLines") + } + + h.lines = opts.Lines + + return h +} + +// handler implements a [slog.Handler]. +type handler struct { + attrsPrefix string + groupPrefix string + groups []string + + mu sync.Mutex + w io.Writer + + addSource bool + level slog.Leveler + replaceAttr func([]string, slog.Attr) slog.Attr + timeFormat string + noColor bool + lines LinesCount +} + +func (h *handler) clone() *handler { + return &handler{ + attrsPrefix: h.attrsPrefix, + groupPrefix: h.groupPrefix, + groups: h.groups, + w: h.w, + addSource: h.addSource, + level: h.level, + replaceAttr: h.replaceAttr, + timeFormat: h.timeFormat, + noColor: h.noColor, + } +} + +func (h *handler) Enabled(_ context.Context, level slog.Level) bool { + return level >= h.level.Level() +} + +// revive:disable:cognitive-complexity we accept this complexity here +// revive:disable:cyclomatic we accept this complexity here +// +//nolint:nestif // We accept this complexity +func (h *handler) Handle(_ context.Context, record slog.Record) error { + // get a buffer from the sync pool + buf := newBuffer() + defer buf.Free() + + rep := h.replaceAttr + + // write time + if !record.Time.IsZero() { + val := record.Time.Round(0) // strip monotonic to match Attr behavior + if rep == nil { + h.appendTime(buf, record.Time) + buf.WriteByte(' ') + } else if a := rep(nil /* groups */, slog.Time(slog.TimeKey, val)); a.Key != "" { + if a.Value.Kind() == slog.KindTime { + h.appendTime(buf, a.Value.Time()) + } else { + h.appendValue(buf, a.Value, false) + } + buf.WriteByte(' ') + } + } + + // write level + if rep == nil { + h.appendLevel(buf, record.Level) + buf.WriteByte(' ') + } else if a := rep(nil /* groups */, slog.Any(slog.LevelKey, record.Level)); a.Key != "" { + h.appendValue(buf, a.Value, false) + buf.WriteByte(' ') + } + + // write message + if rep == nil { + buf.WriteString(record.Message) + buf.WriteByte(' ') + } else if a := rep(nil /* groups */, slog.String(slog.MessageKey, record.Message)); a.Key != "" { + h.appendValue(buf, a.Value, false) + buf.WriteByte(' ') + } + + // write handler attributes + if len(h.attrsPrefix) > 0 { + buf.WriteString(h.attrsPrefix) + } + + // write attributes + record.Attrs(func(attr slog.Attr) bool { + h.appendAttr(buf, attr, h.groupPrefix, h.groups) + + return true + }) + + if len(*buf) == 0 { + return nil + } + + if h.lines == TwoLines { + (*buf)[len(*buf)-1] = '\n' // replace last space with newline + } + + // write source + if h.addSource { + fs := runtime.CallersFrames([]uintptr{record.PC}) + f, _ := fs.Next() + if f.File != "" { + src := &slog.Source{ + Function: f.Function, + File: f.File, + Line: f.Line, + } + + if rep == nil { + h.appendSource(buf, src) + buf.WriteByte(' ') + } else if a := rep(nil /* groups */, slog.Any(slog.SourceKey, src)); a.Key != "" { + h.appendValue(buf, a.Value, false) + buf.WriteByte(' ') + } + } + } + buf.WriteString("\n") + + h.mu.Lock() + defer h.mu.Unlock() + + _, err := h.w.Write(*buf) + if err != nil { + return fmt.Errorf("failed to write log: %w", err) + } + + return nil +} + +// revive:enable + +func (h *handler) WithAttrs(attrs []slog.Attr) slog.Handler { + if len(attrs) == 0 { + return h + } + h2 := h.clone() + + buf := newBuffer() + defer buf.Free() + + // write attributes to buffer + for _, attr := range attrs { + h.appendAttr(buf, attr, h.groupPrefix, h.groups) + } + h2.attrsPrefix = h.attrsPrefix + string(*buf) + + return h2 +} + +func (h *handler) WithGroup(name string) slog.Handler { + if name == "" { + return h + } + h2 := h.clone() + h2.groupPrefix += name + "." + h2.groups = append(h2.groups, name) + + return h2 +} + +func (h *handler) appendTime(buf *buffer, t time.Time) { + buf.WriteStringIf(!h.noColor, ansiFaint) + *buf = t.AppendFormat(*buf, h.timeFormat) + buf.WriteStringIf(!h.noColor, ansiReset) +} + +func (h *handler) appendLevel(buf *buffer, level slog.Level) { + switch { + case level < slog.LevelInfo: + buf.WriteString("DBG") + appendLevelDelta(buf, level-slog.LevelDebug) + case level < slog.LevelWarn: + buf.WriteStringIf(!h.noColor, ansiBrightGreen) + buf.WriteString("INF") + appendLevelDelta(buf, level-slog.LevelInfo) + buf.WriteStringIf(!h.noColor, ansiReset) + case level < slog.LevelError: + buf.WriteStringIf(!h.noColor, ansiBrightYellow) + buf.WriteString("WRN") + appendLevelDelta(buf, level-slog.LevelWarn) + buf.WriteStringIf(!h.noColor, ansiReset) + default: + buf.WriteStringIf(!h.noColor, ansiBrightRed) + buf.WriteString("ERR") + appendLevelDelta(buf, level-slog.LevelError) + buf.WriteStringIf(!h.noColor, ansiReset) + } +} + +func appendLevelDelta(buf *buffer, delta slog.Level) { + if delta == 0 { + return + } else if delta > 0 { + buf.WriteByte('+') + } + *buf = strconv.AppendInt(*buf, int64(delta), 10) +} + +func (h *handler) appendSource(buf *buffer, src *slog.Source) { + // dir, file := filepath.Split(src.File) + + buf.WriteStringIf(!h.noColor, ansiFaint) + // Just add a space before the file path to enable to open the file from the IDE + p := strings.Split(src.File, strings.ToLower(version.AppName)+"/") + if len(p) > 1 { + buf.WriteString(p[1]) + } else { + buf.WriteString(src.File) + } + // buf.WriteString(filepath.Join(filepath.Base(dir), file)) + buf.WriteByte(':') + buf.WriteString(strconv.Itoa(src.Line)) + buf.WriteStringIf(!h.noColor, ansiReset) +} + +func (h *handler) appendAttr(buf *buffer, attr slog.Attr, groupsPrefix string, groups []string) { + attr.Value = attr.Value.Resolve() + if rep := h.replaceAttr; rep != nil && attr.Value.Kind() != slog.KindGroup { + attr = rep(groups, attr) + attr.Value = attr.Value.Resolve() + } + + if attr.Equal(slog.Attr{}) { + return + } + + var err *tintError + tintErr, ok := attr.Value.Any().(tintError) + if attr.Value.Kind() == slog.KindGroup { + if attr.Key != "" { + groupsPrefix += attr.Key + "." + groups = append(groups, attr.Key) + } + for _, groupAttr := range attr.Value.Group() { + h.appendAttr(buf, groupAttr, groupsPrefix, groups) + } + } else if ok && errors.As(tintErr, err) { + // append tintError + h.appendTintError(buf, err, attr.Key, groupsPrefix) + buf.WriteByte(' ') + } else { + h.appendKey(buf, attr.Key, groupsPrefix) + h.appendValue(buf, attr.Value, true) + buf.WriteByte(' ') + } +} + +func (h *handler) appendKey(buf *buffer, key, groups string) { + buf.WriteStringIf(!h.noColor, ansiFaint) + appendString(buf, groups+key, true) + buf.WriteByte('=') + buf.WriteStringIf(!h.noColor, ansiReset) +} + +// revive:disable:cyclomatic we accept this complexity here and don't seems to be so compex +// +//nolint:funlen // We accept this complexity +func (h *handler) appendValue(buf *buffer, value slog.Value, quote bool) { + switch value.Kind() { + case slog.KindString: + appendString(buf, value.String(), quote) + case slog.KindInt64: + *buf = strconv.AppendInt(*buf, value.Int64(), 10) + case slog.KindUint64: + *buf = strconv.AppendUint(*buf, value.Uint64(), 10) + case slog.KindFloat64: + *buf = strconv.AppendFloat(*buf, value.Float64(), 'g', -1, 64) + case slog.KindBool: + *buf = strconv.AppendBool(*buf, value.Bool()) + case slog.KindDuration: + appendString(buf, value.Duration().String(), quote) + case slog.KindTime: + appendString(buf, value.Time().String(), quote) + case slog.KindAny: + switch cv := value.Any().(type) { + case slog.Level: + h.appendLevel(buf, cv) + case encoding.TextMarshaler: + data, err := cv.MarshalText() + if err != nil { + break + } + appendString(buf, string(data), quote) + case *slog.Source: + h.appendSource(buf, cv) + default: + appendString(buf, fmt.Sprintf("%+v", value.Any()), quote) + } + case slog.KindGroup: + attrs := value.Group() + *buf = append(*buf, '{') + for i, attr := range attrs { + if i > 0 { + *buf = append(*buf, ',') + } + appendString(buf, attr.Key, true) + *buf = append(*buf, '=') + h.appendValue(buf, attr.Value, true) + } + *buf = append(*buf, '}') + case slog.KindLogValuer: + resolvedValue := value.LogValuer().LogValue() + h.appendValue(buf, resolvedValue, quote) + } +} + +func (h *handler) appendTintError(buf *buffer, err error, attrKey, groupsPrefix string) { + buf.WriteStringIf(!h.noColor, ansiBrightRedFaint) + appendString(buf, groupsPrefix+attrKey, true) + buf.WriteByte('=') + buf.WriteStringIf(!h.noColor, ansiResetFaint) + appendString(buf, err.Error(), true) + buf.WriteStringIf(!h.noColor, ansiReset) +} + +// revive:disable:flag-parameter in this case we consider is ok to use a flag as a parameter +func appendString(buf *buffer, s string, quote bool) { + if quote && needsQuoting(s) { + *buf = strconv.AppendQuote(*buf, s) + } else { + buf.WriteString(s) + } +} + +// revive:enable + +func needsQuoting(s string) bool { + if len(s) == 0 { + return true + } + for _, r := range s { + if unicode.IsSpace(r) || r == '"' || r == '=' || !unicode.IsPrint(r) { + return true + } + } + + return false +} + +type tintError struct{ error } + +// Err returns a tinted (colorized) [slog.Attr] that will be written in red color +// by the [tint.Handler]. When used with any other [slog.Handler], it behaves as +// +// slog.Any("err", err) +func Err(err error) slog.Attr { + if err != nil { + err = tintError{err} + } + + return slog.Any(errKey, err) +} diff --git a/src/common/tracerr/tracerr.go b/src/common/tracerr/tracerr.go new file mode 100644 index 0000000..dc77304 --- /dev/null +++ b/src/common/tracerr/tracerr.go @@ -0,0 +1,144 @@ +// Package tracerr provides a custom error type that includes a stack trace +package tracerr + +import ( + "errors" + "fmt" + "runtime" + "strings" +) + +// StackTraceError is a custom error type that includes a stack trace +type StackTraceError struct { + Err error + Stack string +} + +// Newf creates a new error with stack trace +func New(msg string) *StackTraceError { + return &StackTraceError{ + Err: fmt.Errorf(msg, nil), + Stack: GetStackTrace(3), + } +} + +// Errorf creates a new formated error with stack trace +func Errorf(format string, a ...any) *StackTraceError { + var e error + if a == nil { + e = errors.New(format) + } else { + e = fmt.Errorf(format, a...) + } + + return &StackTraceError{ + Err: e, + Stack: GetStackTrace(3), + } +} + +func (e *StackTraceError) Error() string { + return fmt.Sprintf("%s\nStack Trace:\n%s", e.Err, e.Stack) +} + +// Unwrap implements the errors.Unwrap interface +func (e *StackTraceError) Unwrap() error { + return e.Err +} + +// Is implements the errors.Is interface +func (e *StackTraceError) Is(target error) bool { + if target == nil { + return false + } + + // Check if the target is also a StackTraceError + t, ok := target.(*StackTraceError) + if !ok { + return errors.Is(e.Err, target) + } + + return errors.Is(e.Err, t.Err) +} + +// As implements the errors.As interface +func (e *StackTraceError) As(target any) bool { + // Try to convert the target to *StackTraceError + if target, ok := target.(*StackTraceError); ok { + *target = *e + + return true + } + + // If target is not *StackTraceError, delegate to the wrapped error + return errors.As(e.Err, target) +} + +// Join creates a new error that wraps the given errors. +// If there is only one error, it returns that error. +// If there are no errors, it returns nil. +func Join(errs ...error) error { + // Filter out nil errors + nonNil := make([]error, 0, len(errs)) + for _, err := range errs { + if err != nil { + nonNil = append(nonNil, err) + } + } + + if len(nonNil) == 0 { + return nil + } + if len(nonNil) == 1 { + return nonNil[0] + } + + // Create a new StackTraceError with joined errors + msg := make([]string, len(nonNil)) + for i, err := range nonNil { + msg[i] = err.Error() + } + + return &StackTraceError{ + Err: errors.Join(nonNil...), + Stack: GetStackTrace(3), + } +} + +// GetStackTrace returns a formatted stack trace similar to gopkg.in/src-d/go-errors.v1 +// It skips the first 'skip' number of frames from the stack trace +func GetStackTrace(skip int) string { + // Allocate space for 32 PCs + pc := make([]uintptr, 32) + + // Get the program counters for the stack trace + n := runtime.Callers(skip, pc) + if n == 0 { + return "" + } + + pc = pc[:n] // Only use PCs that were actually filled + + // Create frames from program counters + frames := runtime.CallersFrames(pc) + + // Build the stack trace string + var builder strings.Builder + + // Process frames until we run out + for { + frame, more := frames.Next() + + // Format this frame according to the specified format + //revive:disable:unhandled-error No need to handle errors here WriteString always returns error nil + builder.WriteString(frame.Function + "\n") + builder.WriteString(fmt.Sprintf("\t%s:%d\n", frame.File, frame.Line)) + //revive:enable + + if !more { + break + } + } + + return builder.String() +} diff --git a/src/domain/model.go b/src/domain/model.go new file mode 100644 index 0000000..1d64341 --- /dev/null +++ b/src/domain/model.go @@ -0,0 +1,25 @@ +// Package domain defines all the domain models +package domain + +import "sync" + +// Put the main models here! + +type Asyncer interface { + Publish(topic string, msg any) + Subscribe(topic string, handler func(topic string, msg []byte)) +} + +type Notifier interface { + SendMsg(chat MessageChannel, text string, status MessageStatus, wg *sync.WaitGroup) +} + +type User struct { + UserID string + Token string + Name string + LastName string + Password string + Email string + Phone string +} diff --git a/src/domain/notify.go b/src/domain/notify.go new file mode 100644 index 0000000..c2008ed --- /dev/null +++ b/src/domain/notify.go @@ -0,0 +1,27 @@ +package domain + +//go:generate go-enum -f=$GOFILE --lower --marshal + +// MessageStatus defines types for messages +// ENUM( +// Good +// Warning +// Stopper +// ) +type MessageStatus int //nolint:recvcheck // The methods of this are autogenerated + +// MessageChannel defines chats to which messages will be sent +// ENUM( +// Test +// Web +// Panic +// Error +// ) +type MessageChannel int //nolint:recvcheck // The methods of this are autogenerated + +type Channels struct { + Test string + Web string + Panic string + Error string +} diff --git a/tools/backup.sh b/tools/backup.sh new file mode 100644 index 0000000..1e6bc9e --- /dev/null +++ b/tools/backup.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# Overwrite the previous backup with the current version + +./qfixpt -v > info + +gzip -k qfixpt + +cp qfixpt.gz "backups" +cp conf.toml "backups" +cp info "backups" + +# Keep a copy with version info + +ver_all=$(./qfixpt -v | grep Build) +arr=("$ver_all") +ver_aux=${arr[1]} +ver=${ver_aux::-1} +echo "$ver" + +mkdir -p "backups/$ver" + +mv qfixpt.gz "backups/$ver" +mv info "backups/$ver" +cp conf.toml "backups/$ver" + +# Only keep the indicated number of backups. Delete the oldests ones. + +i=0 +keep=10 + +for d in $(ls -dt backups/*/); do + if [[ "$i" -gt "$keep" ]]; then + echo "Deleting: ${d}" + else + echo "Keep: ${d}" + fi + ((i++)) +done + diff --git a/tools/branch.sh b/tools/branch.sh new file mode 100755 index 0000000..66265bc --- /dev/null +++ b/tools/branch.sh @@ -0,0 +1,156 @@ +#!/bin/bash + +# Configuration +USER_FILE="${HOME:?HOME not set}/.qtx_branch_last_user" +VALID_TYPES=("feature" "hotfix" "refactor" "improvement" "doc" "fix") + +# Trap for clean Ctrl+C handling +trap 'echo ""; echo "Cancelled."; exit 130' INT TERM + +# Check dependencies +if ! command -v git &> /dev/null; then + echo "Error: 'git' command not found" >&2 + exit 1 +fi + +# Check we're in project root +if [[ ! -f "Makefile" ]]; then + echo "Error: Makefile not found. Run this script from project root." >&2 + exit 1 +fi + +echo "=== Make Branch Helper ===" +echo "" + +# Load last user safely using shell built-in +LAST_USER="" +if [[ -f "$USER_FILE" && -r "$USER_FILE" && ! -L "$USER_FILE" ]]; then + # Use shell built-in instead of cat, only read first line + LAST_USER=$(head -n 1 "$USER_FILE" 2>/dev/null || true) +fi + +if [[ -n "$LAST_USER" ]]; then + echo "Last user found: $LAST_USER" +fi + +# Select branch type +echo "" +echo "Select branch type:" +select TYPE in "${VALID_TYPES[@]}"; do + if [[ -n "$TYPE" ]]; then + echo "Selected type: $TYPE" + break + else + echo "Invalid selection. Please try again." + fi +done + +# Get user with validation +echo "" +while true; do + read -r -p "Enter user (alphanumeric, _, or -) [${LAST_USER:-none}]: " USER + USER="${USER:-$LAST_USER}" + + if [[ -z "$USER" ]]; then + echo "Error: user is required (e.g., jdoe, fsmith)" >&2 + continue + fi + + if [[ ! "$USER" =~ ^[a-zA-Z0-9_-]+$ ]]; then + echo "Error: user must be alphanumeric with optional _ or - (e.g., jdoe, f_smith)" >&2 + continue + fi + + break +done + +# Save user preference securely with umask +if [[ -n "$HOME" ]]; then + # Remove if it's a symlink (prevent symlink attacks) + [[ -L "$USER_FILE" ]] && rm -f "$USER_FILE" + + # Create with restricted permissions (600) + (umask 077 && echo "$USER" > "$USER_FILE") +fi + +# Get issue number with numeric validation +echo "" +while true; do + read -r -p "Enter issue number (numeric only): " ISSUE + + if [[ -z "$ISSUE" ]]; then + echo "Error: issue number is required (e.g., 123, 456)" >&2 + continue + fi + + if [[ ! "$ISSUE" =~ ^[0-9]+$ ]]; then + echo "Error: issue number must be numeric only (e.g., 39, 123)" >&2 + continue + fi + + break +done + +# Get summary with validation +echo "" +while true; do + read -r -p "Enter summary (max 100 chars): " SUMMARY + + # Trim leading/trailing whitespace + SUMMARY=$(echo "$SUMMARY" | xargs) + + if [[ -z "$SUMMARY" ]]; then + echo "Error: summary is required (e.g., 'add new endpoint', 'fix login bug')" >&2 + continue + fi + + if [[ ${#SUMMARY} -gt 100 ]]; then + echo "Error: summary too long (${#SUMMARY} chars, max 100)" >&2 + continue + fi + + # Basic validation - allow common characters + if [[ ! "$SUMMARY" =~ ^[a-zA-Z0-9\ _\-]+$ ]]; then + echo "Warning: summary contains special characters. Use only letters, numbers, spaces, hyphens, and underscores." >&2 + read -r -p "Continue anyway? (y/N): " CONFIRM + if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then + continue + fi + fi + + break +done + +# Get ISSUE_PREFIX from Makefile.common (default to SKL if not found) +ISSUE_PREFIX="SKL" +if [[ -f "Makefile.common" ]]; then + # Extract ISSUE_PREFIX from Makefile.common + PREFIX_LINE=$(grep "^ISSUE_PREFIX" Makefile.common 2>/dev/null || true) + if [[ -n "$PREFIX_LINE" ]]; then + ISSUE_PREFIX=$(echo "$PREFIX_LINE" | cut -d'=' -f2 | tr -d ' ') + fi +fi + +# Build branch name: type/user/PREFIX-number/summary_with_underscores +BRANCH_NAME="$TYPE/$USER/$ISSUE_PREFIX-$ISSUE/${SUMMARY// /_}" + +# Show summary and execute +echo "" +echo "Creating branch:" +echo " Type: $TYPE" +echo " User: $USER" +echo " Issue: $ISSUE_PREFIX-$ISSUE" +echo " Summary: $SUMMARY" +echo " Branch: $BRANCH_NAME" +echo "" + +# Create the git branch +if git checkout -b "$BRANCH_NAME"; then + echo "" + echo "βœ“ Branch '$BRANCH_NAME' created and checked out successfully" + exit 0 +else + echo "" >&2 + echo "βœ— Failed to create branch '$BRANCH_NAME'" >&2 + exit 1 +fi diff --git a/tools/build.sh b/tools/build.sh new file mode 100755 index 0000000..766da5f --- /dev/null +++ b/tools/build.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +SkipCheckRepo=false + +if [ -z "$SkipCheckRepo" ] && [ -n "$(git status --porcelain)" ]; then + echo "Repo is dirty. Aborting..." + exit 1 +fi + +BUILT_TIME=$(date +'%B-%d,-%Y-@-%T') +BUILD_HASH=$(git rev-parse HEAD) +if [ "$CI_COMMIT_BRANCH" ]; then + HOSTNAME="GitLab" + BUILD_BRANCH=$CI_COMMIT_BRANCH +else + HOSTNAME=$(hostname -f) + BUILD_BRANCH=$(git rev-parse --abbrev-ref HEAD) +fi + +# This allow to set the default path, if script used outside makefile +if [ -z "$OUT_PATH" ]; then + OUT_PATH="./build/out/distribution" +fi + +mkdir -p "$OUT_PATH" + +go generate ./... + +# Generate the native version +env CGO_ENABLED=0 go build -mod vendor -o ${OUT_PATH}/qfixpt -ldflags "-X quantex.com/qfixpt/src/app/version.hostname=${HOSTNAME} -X quantex.com/qfixpt/src/app/version.builtTime=${BUILT_TIME} -X quantex.com/qfixpt/src/app/version.buildHash=${BUILD_HASH} -X quantex.com/qfixpt/src/app/version.buildBranch=${BUILD_BRANCH}" *.go + +gzip -kf "${OUT_PATH}"/qfixpt + +# if no environment argument, set to default value dev +if [ -z "$1" ]; then + ENV="dev" +else + ENV="$1" +fi + +if COMMIT_MSG=$(QUANTEX_ENVIRONMENT=$ENV "${OUT_PATH}/qfixpt" -v 2>/dev/null); then + echo "$COMMIT_MSG" +else + echo "---------------------------------" + echo "Skeleton" + echo "Built at: ${BUILT_TIME}" + echo "Branch: ${BUILD_BRANCH}" + echo "SHA: ${BUILD_HASH}" + echo "Built from ${HOSTNAME}" + echo "Powered by Quantex Technologies" + echo "---------------------------------" +fi + diff --git a/tools/check/check-gogenerate.sh b/tools/check/check-gogenerate.sh new file mode 100755 index 0000000..fb55ae5 --- /dev/null +++ b/tools/check/check-gogenerate.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euo pipefail + +go generate ./... +set +e +diffline=$(git status -s | awk '{print $2}' | xargs grep '^// Code generated .* DO NOT EDIT\.$' 2>/dev/null) +set -e +if [[ $diffline != "" ]] +then + echo "Your commit is changed after running go generate ./..., it should not happen." + exit 1 +fi diff --git a/tools/check/check-tidy.sh b/tools/check/check-tidy.sh new file mode 100755 index 0000000..4fe2c0f --- /dev/null +++ b/tools/check/check-tidy.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. +# +# set is used to set the environment variables. +# -e: exit immediately when a command returning a non-zero exit code. +# -u: treat unset variables as an error. +# -o pipefail: sets the exit code of a pipeline to that of the rightmost command to exit with a non-zero status, +# or to zero if all commands of the pipeline exit successfully. +set -euo pipefail + +# go mod tidy do not support symlink +cd -P . + +cp go.sum /tmp/go.sum.before +go mod tidy | diff -q go.sum /tmp/go.sum.before diff --git a/tools/check/check_parser_replace.sh b/tools/check/check_parser_replace.sh new file mode 100755 index 0000000..efb7ca2 --- /dev/null +++ b/tools/check/check_parser_replace.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -uo pipefail + +grep "replace.*github.com/pingcap/parser" go.mod +grep_ret=$? + +if [ $grep_ret -eq 0 ];then + exit 1 +else + exit 0 +fi diff --git a/tools/check/errcheck_excludes.txt b/tools/check/errcheck_excludes.txt new file mode 100644 index 0000000..9fdbf63 --- /dev/null +++ b/tools/check/errcheck_excludes.txt @@ -0,0 +1,7 @@ +fmt.Fprintf +fmt.Fprint +fmt.Sscanf +quantex.com/qfixpt/src/client/tracerr.Errorf +(*quantex.com/qfixpt/src/common/logger/tint.buffer).WriteByte +(*quantex.com/qfixpt/src/common/logger/tint.buffer).WriteString +(*quantex.com/qfixpt/src/common/logger/tint.buffer).WriteStringIf diff --git a/tools/check/go.mod b/tools/check/go.mod new file mode 100644 index 0000000..3085042 --- /dev/null +++ b/tools/check/go.mod @@ -0,0 +1,230 @@ +module quanticko.com/qfixpt/_tools + +go 1.23 + +require honnef.co/go/tools v0.5.1 // indirect staticcheck + +require ( + 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect + 4d63.com/gochecknoglobals v0.2.1 // indirect + cloud.google.com/go v0.116.0 // indirect + cloud.google.com/go/ai v0.8.2 // indirect + cloud.google.com/go/auth v0.10.1 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.5 // indirect + cloud.google.com/go/compute/metadata v0.5.2 // indirect + cloud.google.com/go/longrunning v0.6.2 // indirect + github.com/4meepo/tagalign v1.3.4 // indirect + github.com/Abirdcfly/dupword v0.1.3 // indirect + github.com/Antonboom/errname v1.0.0 // indirect + github.com/Antonboom/nilnil v1.0.0 // indirect + github.com/Antonboom/testifylint v1.5.0 // indirect + github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect + github.com/Crocmagnon/fatcontext v0.5.2 // indirect + github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect + github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect + github.com/alecthomas/go-check-sumtype v0.2.0 // indirect + github.com/alexkohler/nakedret/v2 v2.0.5 // indirect + github.com/alexkohler/prealloc v1.0.0 // indirect + github.com/alingse/asasalint v0.0.11 // indirect + github.com/ashanbrown/forbidigo v1.6.0 // indirect + github.com/ashanbrown/makezero v1.1.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bkielbasa/cyclop v1.2.3 // indirect + github.com/blizzy78/varnamelen v0.8.0 // indirect + github.com/bombsimon/wsl/v4 v4.4.1 // indirect + github.com/breml/bidichk v0.3.2 // indirect + github.com/breml/errchkjson v0.4.0 // indirect + github.com/butuzov/ireturn v0.3.0 // indirect + github.com/butuzov/mirror v1.2.0 // indirect + github.com/catenacyber/perfsprint v0.7.1 // indirect + github.com/ccojocar/zxcvbn-go v1.0.2 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/charithe/durationcheck v0.0.10 // indirect + github.com/chavacava/garif v0.1.0 // indirect + github.com/chzchzchz/goword v0.0.0-20170907005317-a9744cb52b03 // indirect + github.com/ckaznocha/intrange v0.2.1 // indirect + github.com/curioswitch/go-reassign v0.2.0 // indirect + github.com/daixiang0/gci v0.13.5 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/denis-tingaikin/go-header v0.5.0 // indirect + github.com/ettle/strcase v0.2.0 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/fatih/structtag v1.2.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/firefart/nonamedreturns v1.0.5 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/fzipp/gocyclo v0.6.0 // indirect + github.com/ghostiam/protogetter v0.3.8 // indirect + github.com/go-critic/go-critic v0.11.5 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-toolsmith/astcast v1.1.0 // indirect + github.com/go-toolsmith/astcopy v1.1.0 // indirect + github.com/go-toolsmith/astequal v1.2.0 // indirect + github.com/go-toolsmith/astfmt v1.1.0 // indirect + github.com/go-toolsmith/astp v1.1.0 // indirect + github.com/go-toolsmith/strparse v1.1.0 // indirect + github.com/go-toolsmith/typep v1.1.0 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/gofrs/flock v0.12.1 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect + github.com/golangci/go-printf-func-name v0.1.0 // indirect + github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 // indirect + github.com/golangci/golangci-lint v1.62.0 // indirect + github.com/golangci/misspell v0.6.0 // indirect + github.com/golangci/modinfo v0.3.4 // indirect + github.com/golangci/plugin-module-register v0.1.1 // indirect + github.com/golangci/revgrep v0.5.3 // indirect + github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect + github.com/google/generative-ai-go v0.18.0 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/s2a-go v0.1.8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/gax-go/v2 v2.13.0 // indirect + github.com/gookit/color v1.5.4 // indirect + github.com/gordonklaus/ineffassign v0.1.0 // indirect + github.com/gostaticanalysis/analysisutil v0.7.1 // indirect + github.com/gostaticanalysis/comment v1.4.2 // indirect + github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect + github.com/gostaticanalysis/nilerr v0.1.1 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hexops/gotextdiff v1.0.3 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jgautheron/goconst v1.7.1 // indirect + github.com/jingyugao/rowserrcheck v1.1.1 // indirect + github.com/jjti/go-spancheck v0.6.2 // indirect + github.com/julz/importas v0.1.0 // indirect + github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect + github.com/kisielk/errcheck v1.8.0 // indirect + github.com/kkHAIKE/contextcheck v1.1.5 // indirect + github.com/kulti/thelper v0.6.3 // indirect + github.com/kunwardeep/paralleltest v1.0.10 // indirect + github.com/kyoh86/exportloopref v0.1.11 // indirect + github.com/lasiar/canonicalheader v1.1.2 // indirect + github.com/ldez/gomoddirectives v0.2.4 // indirect + github.com/ldez/tagliatelle v0.5.0 // indirect + github.com/leonklingele/grouper v1.1.2 // indirect + github.com/macabu/inamedparam v0.1.3 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/maratori/testableexamples v1.0.0 // indirect + github.com/maratori/testpackage v1.1.1 // indirect + github.com/matoous/godox v0.0.0-20240105082147-c5b5e0e7c0c0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mdempsky/unconvert v0.0.0-20230907125504-415706980c06 // indirect + github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect + github.com/mgechev/revive v1.5.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moricho/tparallel v0.3.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/nakabonne/nestif v0.3.1 // indirect + github.com/nishanths/exhaustive v0.12.0 // indirect + github.com/nishanths/predeclared v0.2.2 // indirect + github.com/nunnatsa/ginkgolinter v0.18.2 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pingcap/errors v0.11.4 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/polyfloyd/go-errorlint v1.6.0 // indirect + github.com/prometheus/client_golang v1.20.5 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.60.1 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect + github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect + github.com/quasilyte/gogrep v0.5.0 // indirect + github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect + github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect + github.com/raeperd/recvcheck v0.1.2 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/ryancurrah/gomodguard v1.3.5 // indirect + github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect + github.com/sagikazarmark/locafero v0.6.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect + github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect + github.com/sashamelentyev/interfacebloat v1.1.0 // indirect + github.com/sashamelentyev/usestdlibvars v1.27.0 // indirect + github.com/securego/gosec/v2 v2.21.4 // indirect + github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sivchari/containedctx v1.0.3 // indirect + github.com/sivchari/tenv v1.12.1 // indirect + github.com/sonatard/noctx v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/sourcegraph/go-diff v0.7.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.7.0 // indirect + github.com/spf13/cobra v1.8.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.19.0 // indirect + github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect + github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/stretchr/testify v1.9.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/swaggo/swag v1.8.9 // indirect + github.com/tdakkota/asciicheck v0.2.0 // indirect + github.com/tetafro/godot v1.4.18 // indirect + github.com/timakin/bodyclose v0.0.0-20241017074824-adbc21e6bf36 // indirect + github.com/timonwong/loggercheck v0.10.1 // indirect + github.com/tomarrell/wrapcheck/v2 v2.9.0 // indirect + github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect + github.com/ultraware/funlen v0.1.0 // indirect + github.com/ultraware/whitespace v0.1.1 // indirect + github.com/uudashr/gocognit v1.1.3 // indirect + github.com/uudashr/iface v1.2.0 // indirect + github.com/xen0n/gosmopolitan v1.2.2 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/yagipy/maintidx v1.0.0 // indirect + github.com/yeya24/promlinter v0.3.0 // indirect + github.com/ykadowak/zerologlint v0.1.5 // indirect + gitlab.com/bosi/decorder v0.4.2 // indirect + go-simpler.org/musttag v0.13.0 // indirect + go-simpler.org/sloglint v0.7.2 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect + go.opentelemetry.io/otel v1.32.0 // indirect + go.opentelemetry.io/otel/metric v1.32.0 // indirect + go.opentelemetry.io/otel/trace v1.32.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/automaxprocs v1.6.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.29.0 // indirect + golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect + golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.31.0 // indirect + golang.org/x/oauth2 v0.24.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/text v0.20.0 // indirect + golang.org/x/time v0.8.0 // indirect + golang.org/x/tools v0.27.0 // indirect + google.golang.org/api v0.205.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/grpc v1.68.0 // indirect + google.golang.org/protobuf v1.35.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + mvdan.cc/gofumpt v0.7.0 // indirect + mvdan.cc/unparam v0.0.0-20240917084806-57a3b4290ba3 // indirect +) diff --git a/tools/check/go.sum b/tools/check/go.sum new file mode 100644 index 0000000..1af8645 --- /dev/null +++ b/tools/check/go.sum @@ -0,0 +1,985 @@ +4d63.com/gocheckcompilerdirectives v1.2.1 h1:AHcMYuw56NPjq/2y615IGg2kYkBdTvOaojYCBcRE7MA= +4d63.com/gocheckcompilerdirectives v1.2.1/go.mod h1:yjDJSxmDTtIHHCqX0ufRYZDL6vQtMG7tJdKVeWwsqvs= +4d63.com/gochecknoglobals v0.2.1 h1:1eiorGsgHOFOuoOiJDy2psSrQbRdIHrlge0IJIkUgDc= +4d63.com/gochecknoglobals v0.2.1/go.mod h1:KRE8wtJB3CXCsb1xy421JfTHIIbmT3U5ruxw2Qu8fSU= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= +cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= +cloud.google.com/go/ai v0.8.2 h1:LEaQwqBv+k2ybrcdTtCTc9OPZXoEdcQaGrfvDYS6Bnk= +cloud.google.com/go/ai v0.8.2/go.mod h1:Wb3EUUGWwB6yHBaUf/+oxUq/6XbCaU1yh0GrwUS8lr4= +cloud.google.com/go/auth v0.10.1 h1:TnK46qldSfHWt2a0b/hciaiVJsmDXWy9FqyUan0uYiI= +cloud.google.com/go/auth v0.10.1/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth/oauth2adapt v0.2.5 h1:2p29+dePqsCHPP1bqDJcKj4qxRyYCcbzKpFyKGt3MTk= +cloud.google.com/go/auth/oauth2adapt v0.2.5/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= +cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= +cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= +cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= +cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc= +cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= +github.com/4meepo/tagalign v1.2.2 h1:kQeUTkFTaBRtd/7jm8OKJl9iHk0gAO+TDFPHGSna0aw= +github.com/4meepo/tagalign v1.2.2/go.mod h1:Q9c1rYMZJc9dPRkbQPpcBNCLEmY2njbAsXhQOZFE2dE= +github.com/4meepo/tagalign v1.3.4 h1:P51VcvBnf04YkHzjfclN6BbsopfJR5rxs1n+5zHt+w8= +github.com/4meepo/tagalign v1.3.4/go.mod h1:M+pnkHH2vG8+qhE5bVc/zeP7HS/j910Fwa9TUSyZVI0= +github.com/Abirdcfly/dupword v0.0.11 h1:z6v8rMETchZXUIuHxYNmlUAuKuB21PeaSymTed16wgU= +github.com/Abirdcfly/dupword v0.0.11/go.mod h1:wH8mVGuf3CP5fsBTkfWwwwKTjDnVVCxtU8d8rgeVYXA= +github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE= +github.com/Abirdcfly/dupword v0.1.3/go.mod h1:8VbB2t7e10KRNdwTVoxdBaxla6avbhGzb8sCTygUMhw= +github.com/Antonboom/errname v0.1.10 h1:RZ7cYo/GuZqjr1nuJLNe8ZH+a+Jd9DaZzttWzak9Bls= +github.com/Antonboom/errname v0.1.10/go.mod h1:xLeiCIrvVNpUtsN0wxAh05bNIZpqE22/qDMnTBTttiA= +github.com/Antonboom/errname v1.0.0 h1:oJOOWR07vS1kRusl6YRSlat7HFnb3mSfMl6sDMRoTBA= +github.com/Antonboom/errname v1.0.0/go.mod h1:gMOBFzK/vrTiXN9Oh+HFs+e6Ndl0eTFbtsRTSRdXyGI= +github.com/Antonboom/nilnil v0.1.5 h1:X2JAdEVcbPaOom2TUa1FxZ3uyuUlex0XMLGYMemu6l0= +github.com/Antonboom/nilnil v0.1.5/go.mod h1:I24toVuBKhfP5teihGWctrRiPbRKHwZIFOvc6v3HZXk= +github.com/Antonboom/nilnil v1.0.0 h1:n+v+B12dsE5tbAqRODXmEKfZv9j2KcTBrp+LkoM4HZk= +github.com/Antonboom/nilnil v1.0.0/go.mod h1:fDJ1FSFoLN6yoG65ANb1WihItf6qt9PJVTn/s2IrcII= +github.com/Antonboom/testifylint v1.5.0 h1:dlUIsDMtCrZWUnvkaCz3quJCoIjaGi41GzjPBGkkJ8A= +github.com/Antonboom/testifylint v1.5.0/go.mod h1:wqaJbu0Blb5Wag2wv7Z5xt+CIV+eVLxtGZrlK13z3AE= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/Crocmagnon/fatcontext v0.5.2 h1:vhSEg8Gqng8awhPju2w7MKHqMlg4/NI+gSDHtR3xgwA= +github.com/Crocmagnon/fatcontext v0.5.2/go.mod h1:87XhRMaInHP44Q7Tlc7jkgKKB7kZAOPiDkFMdKCC+74= +github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= +github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= +github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 h1:+r1rSv4gvYn0wmRjC8X7IAzX8QezqtFV9m0MUHFJgts= +github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0/go.mod h1:b3g59n2Y+T5xmcxJL+UEG2f8cQploZm1mR/v6BW0mU0= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 h1:/fTUt5vmbkAcMBt4YQiuC23cV0kEsN1MVMNqeOW43cU= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0/go.mod h1:ONJg5sxcbsdQQ4pOW8TGdTidT2TMAUy/2Xhr8mrYaao= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/OpenPeeDeeP/depguard/v2 v2.1.0 h1:aQl70G173h/GZYhWf36aE5H0KaujXfVMnn/f1kSDVYY= +github.com/OpenPeeDeeP/depguard/v2 v2.1.0/go.mod h1:PUBgk35fX4i7JDmwzlJwJ+GMe6NfO1723wmJMgPThNQ= +github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= +github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= +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/alecthomas/go-check-sumtype v0.2.0 h1:Bo+e4DFf3rs7ME9w/0SU/g6nmzJaphduP8Cjiz0gbwY= +github.com/alecthomas/go-check-sumtype v0.2.0/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alexkohler/nakedret/v2 v2.0.2 h1:qnXuZNvv3/AxkAb22q/sEsEpcA99YxLFACDtEw9TPxE= +github.com/alexkohler/nakedret/v2 v2.0.2/go.mod h1:2b8Gkk0GsOrqQv/gPWjNLDSKwG8I5moSXG1K4VIBcTQ= +github.com/alexkohler/nakedret/v2 v2.0.5 h1:fP5qLgtwbx9EJE8dGEERT02YwS8En4r9nnZ71RK+EVU= +github.com/alexkohler/nakedret/v2 v2.0.5/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= +github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= +github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= +github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= +github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= +github.com/ashanbrown/forbidigo v1.5.3 h1:jfg+fkm/snMx+V9FBwsl1d340BV/99kZGv5jN9hBoXk= +github.com/ashanbrown/forbidigo v1.5.3/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= +github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= +github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= +github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= +github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bkielbasa/cyclop v1.2.1 h1:AeF71HZDob1P2/pRm1so9cd1alZnrpyc4q2uP2l0gJY= +github.com/bkielbasa/cyclop v1.2.1/go.mod h1:K/dT/M0FPAiYjBgQGau7tz+3TMh4FWAEqlMhzFWCrgM= +github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= +github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo= +github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= +github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= +github.com/bombsimon/wsl/v3 v3.4.0 h1:RkSxjT3tmlptwfgEgTgU+KYKLI35p/tviNXNXiL2aNU= +github.com/bombsimon/wsl/v3 v3.4.0/go.mod h1:KkIB+TXkqy6MvK9BDZVbZxKNYsE1/oLRJbIFtf14qqo= +github.com/bombsimon/wsl/v4 v4.4.1 h1:jfUaCkN+aUpobrMO24zwyAMwMAV5eSziCkOKEauOLdw= +github.com/bombsimon/wsl/v4 v4.4.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= +github.com/breml/bidichk v0.2.4 h1:i3yedFWWQ7YzjdZJHnPo9d/xURinSq3OM+gyM43K4/8= +github.com/breml/bidichk v0.2.4/go.mod h1:7Zk0kRFt1LIZxtQdl9W9JwGAcLTTkOs+tN7wuEYGJ3s= +github.com/breml/bidichk v0.3.2 h1:xV4flJ9V5xWTqxL+/PMFF6dtJPvZLPsyixAoPe8BGJs= +github.com/breml/bidichk v0.3.2/go.mod h1:VzFLBxuYtT23z5+iVkamXO386OB+/sVwZOpIj6zXGos= +github.com/breml/errchkjson v0.3.1 h1:hlIeXuspTyt8Y/UmP5qy1JocGNR00KQHgfaNtRAjoxQ= +github.com/breml/errchkjson v0.3.1/go.mod h1:XroxrzKjdiutFyW3nWhw34VGg7kiMsDQox73yWCGI2U= +github.com/breml/errchkjson v0.4.0 h1:gftf6uWZMtIa/Is3XJgibewBm2ksAQSY/kABDNFTAdk= +github.com/breml/errchkjson v0.4.0/go.mod h1:AuBOSTHyLSaaAFlWsRSuRBIroCh3eh7ZHh5YeelDIk8= +github.com/butuzov/ireturn v0.2.0 h1:kCHi+YzC150GE98WFuZQu9yrTn6GEydO2AuPLbTgnO4= +github.com/butuzov/ireturn v0.2.0/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= +github.com/butuzov/ireturn v0.3.0 h1:hTjMqWw3y5JC3kpnC5vXmFJAWI/m31jaCYQqzkS6PL0= +github.com/butuzov/ireturn v0.3.0/go.mod h1:A09nIiwiqzN/IoVo9ogpa0Hzi9fex1kd9PSD6edP5ZA= +github.com/butuzov/mirror v1.1.0 h1:ZqX54gBVMXu78QLoiqdwpl2mgmoOJTk7s4p4o+0avZI= +github.com/butuzov/mirror v1.1.0/go.mod h1:8Q0BdQU6rC6WILDiBM60DBfvV78OLJmMmixe7GF45AE= +github.com/butuzov/mirror v1.2.0 h1:9YVK1qIjNspaqWutSv8gsge2e/Xpq1eqEkslEUHy5cs= +github.com/butuzov/mirror v1.2.0/go.mod h1:DqZZDtzm42wIAIyHXeN8W/qb1EPlb9Qn/if9icBOpdQ= +github.com/catenacyber/perfsprint v0.7.1 h1:PGW5G/Kxn+YrN04cRAZKC+ZuvlVwolYMrIyyTJ/rMmc= +github.com/catenacyber/perfsprint v0.7.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= +github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= +github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +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/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= +github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= +github.com/chavacava/garif v0.0.0-20220630083739-93517212f375 h1:E7LT642ysztPWE0dfz43cWOvMiF42DyTRC+eZIaO4yI= +github.com/chavacava/garif v0.0.0-20220630083739-93517212f375/go.mod h1:4m1Rv7xfuwWPNKXlThldNuJvutYM6J95wNuuVmn55To= +github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 h1:W9o46d2kbNL06lq7UNDPV0zYLzkrde/bjIqO02eoll0= +github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8/go.mod h1:gakxgyXaaPkxvLw1XQxNGK4I37ys9iBRzNUx/B7pUCo= +github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= +github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= +github.com/chzchzchz/goword v0.0.0-20170907005317-a9744cb52b03 h1:0wUHjDfbCAROEAZ96zAJGwcNMkPIheFaIjtQyv3QqfM= +github.com/chzchzchz/goword v0.0.0-20170907005317-a9744cb52b03/go.mod h1:uFE9hX+zXEwvyUThZ4gDb9vkAwc5DoHUnRSEpH0VrOs= +github.com/ckaznocha/intrange v0.2.1 h1:M07spnNEQoALOJhwrImSrJLaxwuiQK+hA2DeajBlwYk= +github.com/ckaznocha/intrange v0.2.1/go.mod h1:7NEhVyf8fzZO5Ds7CRaqPEm52Ut83hsTiL5zbER/HYk= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= +github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= +github.com/daixiang0/gci v0.9.0 h1:t8XZ0vK6l0pwPoOmoGyqW2NwQlvbpAQNVvu/GRBgykM= +github.com/daixiang0/gci v0.9.0/go.mod h1:EpVfrztufwVgQRXjnX4zuNinEpLj5OmMjtu/+MB0V0c= +github.com/daixiang0/gci v0.10.1 h1:eheNA3ljF6SxnPD/vE4lCBusVHmV3Rs3dkKvFrJ7MR0= +github.com/daixiang0/gci v0.10.1/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI= +github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c= +github.com/daixiang0/gci v0.13.5/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= +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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denis-tingaikin/go-header v0.4.3 h1:tEaZKAlqql6SKCY++utLmkPLd6K8IBM20Ha7UVm+mtU= +github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c= +github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= +github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/esimonov/ifshort v1.0.4 h1:6SID4yGWfRae/M7hkVDVVyppy8q/v9OuxNdmjLQStBA= +github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= +github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= +github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= +github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= +github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= +github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= +github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= +github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= +github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghostiam/protogetter v0.3.8 h1:LYcXbYvybUyTIxN2Mj9h6rHrDZBDwZloPoKctWrFyJY= +github.com/ghostiam/protogetter v0.3.8/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= +github.com/go-critic/go-critic v0.8.1 h1:16omCF1gN3gTzt4j4J6fKI/HnRojhEp+Eks6EuKw3vw= +github.com/go-critic/go-critic v0.8.1/go.mod h1:kpzXl09SIJX1cr9TB/g/sAG+eFEl7ZS9f9cqvZtyNl0= +github.com/go-critic/go-critic v0.11.5 h1:TkDTOn5v7EEngMxu8KbuFqFR43USaaH8XRJLz1jhVYA= +github.com/go-critic/go-critic v0.11.5/go.mod h1:wu6U7ny9PiaHaZHcvMDmdysMqvDem162Rh3zWTrqk8M= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/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.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.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= +github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= +github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= +github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= +github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= +github.com/go-toolsmith/astequal v1.1.0 h1:kHKm1AWqClYn15R0K1KKE4RG614D46n+nqUQ06E1dTw= +github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= +github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw= +github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY= +github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco= +github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= +github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= +github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= +github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= +github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= +github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U= +github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= +github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe h1:6RGUuS7EGotKx6J5HIP8ZtyMdiDscjMLfRBSPuzVVeo= +github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe/go.mod h1:gjqyPShc/m8pEMpk0a3SeagVb0kaqvhscv+i9jI5ZhQ= +github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= +github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= +github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2 h1:amWTbTGqOZ71ruzrdA+Nx5WA3tV1N0goTspwmKCQvBY= +github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2/go.mod h1:9wOXstvyDRshQ9LggQuzBCGysxs3b6Uo/1MvYCR2NMs= +github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 h1:/1322Qns6BtQxUZDTAT4SdcoxknUki7IAoK4SAXr8ME= +github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9/go.mod h1:Oesb/0uFAyWoaw1U1qS5zyjCg5NP9C9iwjnI4tIsXEE= +github.com/golangci/golangci-lint v1.53.3 h1:CUcRafczT4t1F+mvdkUm6KuOpxUZTl0yWN/rSU6sSMo= +github.com/golangci/golangci-lint v1.53.3/go.mod h1:W4Gg3ONq6p3Jl+0s/h9Gr0j7yEgHJWWZO2bHl2tBUXM= +github.com/golangci/golangci-lint v1.62.0 h1:/G0g+bi1BhmGJqLdNQkKBWjcim8HjOPc4tsKuHDOhcI= +github.com/golangci/golangci-lint v1.62.0/go.mod h1:jtoOhQcKTz8B6dGNFyfQV3WZkQk+YvBDewDtNpiAJts= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= +github.com/golangci/misspell v0.4.0 h1:KtVB/hTK4bbL/S6bs64rYyk8adjmh1BygbBiaAiX+a0= +github.com/golangci/misspell v0.4.0/go.mod h1:W6O/bwV6lGDxUCChm2ykw9NQdd5bYd1Xkjo88UcWyJc= +github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= +github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= +github.com/golangci/modinfo v0.3.4 h1:oU5huX3fbxqQXdfspamej74DFX0kyGLkw1ppvXoJ8GA= +github.com/golangci/modinfo v0.3.4/go.mod h1:wytF1M5xl9u0ij8YSvhkEVPP3M5Mc7XLl1pxH3B2aUM= +github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= +github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= +github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 h1:DIPQnGy2Gv2FSA4B/hh8Q7xx3B7AIDk3DAMeHclH1vQ= +github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6/go.mod h1:0AKcRCkMoKvUvlf89F6O7H2LYdhr1zBh736mBItOdRs= +github.com/golangci/revgrep v0.5.3 h1:3tL7c1XBMtWHHqVpS5ChmiAAoe4PF/d5+ULzV9sLAzs= +github.com/golangci/revgrep v0.5.3/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= +github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs= +github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= +github.com/google/generative-ai-go v0.18.0 h1:6ybg9vOCLcI/UpBBYXOTVgvKmcUKFRNj+2Cj3GnebSo= +github.com/google/generative-ai-go v0.18.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= +github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= +github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI= +github.com/gookit/color v1.5.3 h1:twfIhZs4QLCtimkP7MOxlF3A0U/5cDPseRT9M/+2SCE= +github.com/gookit/color v1.5.3/go.mod h1:NUzwzeehUfl7GIb36pqId+UGmRfQcU/WiiyTTeNjHtE= +github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= +github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= +github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 h1:mrEEilTAUmaAORhssPPkxj84TsHrPMLBGW2Z4SoTxm8= +github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= +github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= +github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= +github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= +github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= +github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= +github.com/gostaticanalysis/comment v1.4.2 h1:hlnx5+S2fY9Zo9ePo4AhgYsYHbM2+eAv8m/s1JiCd6Q= +github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= +github.com/gostaticanalysis/forcetypeassert v0.1.0 h1:6eUflI3DiGusXGK6X7cCcIgVCpZ2CiZ1Q7jl6ZxNV70= +github.com/gostaticanalysis/forcetypeassert v0.1.0/go.mod h1:qZEedyP/sY1lTGV1uJ3VhWZ2mqag3IkWsDHVbplHXak= +github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= +github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= +github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +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/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/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jgautheron/goconst v1.5.1 h1:HxVbL1MhydKs8R8n/HE5NPvzfaYmQJA3o879lE4+WcM= +github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= +github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk= +github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= +github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= +github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= +github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48= +github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= +github.com/jjti/go-spancheck v0.6.2 h1:iYtoxqPMzHUPp7St+5yA8+cONdyXD3ug6KK15n7Pklk= +github.com/jjti/go-spancheck v0.6.2/go.mod h1:+X7lvIrR5ZdUTkxFYqzJ0abr8Sb5LOo80uOhWNqIrYA= +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/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= +github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= +github.com/karamaru-alpha/copyloopvar v1.1.0 h1:x7gNyKcC2vRBO1H2Mks5u1VxQtYvFiym7fCjIP8RPos= +github.com/karamaru-alpha/copyloopvar v1.1.0/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= +github.com/kisielk/errcheck v1.6.3 h1:dEKh+GLHcWm2oN34nMvDzn1sqI0i0WxPvrgiJA5JuM8= +github.com/kisielk/errcheck v1.6.3/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw= +github.com/kisielk/errcheck v1.8.0 h1:ZX/URYa7ilESY19ik/vBmCn6zdGQLxACwjAcWbHlYlg= +github.com/kisielk/errcheck v1.8.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkHAIKE/contextcheck v1.1.4 h1:B6zAaLhOEEcjvUgIYEqystmnFk1Oemn8bvJhbt0GMb8= +github.com/kkHAIKE/contextcheck v1.1.4/go.mod h1:1+i/gWqokIa+dm31mqGLZhZJ7Uh44DJGZVmr6QRBNJg= +github.com/kkHAIKE/contextcheck v1.1.5 h1:CdnJh63tcDe53vG+RebdpdXJTc9atMgGqdx8LXxiilg= +github.com/kkHAIKE/contextcheck v1.1.5/go.mod h1:O930cpht4xb1YQpK+1+AgoM3mFsvxr7uyFptcnWTYUA= +github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= +github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= +github.com/kunwardeep/paralleltest v1.0.7 h1:2uCk94js0+nVNQoHZNLBkAR1DQJrVzw6T0RMzJn55dQ= +github.com/kunwardeep/paralleltest v1.0.7/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= +github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCTdvWJ/lDDs= +github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= +github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ= +github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA= +github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= +github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= +github.com/ldez/gomoddirectives v0.2.3 h1:y7MBaisZVDYmKvt9/l1mjNCiSA1BVn34U0ObUcJwlhA= +github.com/ldez/gomoddirectives v0.2.3/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0= +github.com/ldez/gomoddirectives v0.2.4 h1:j3YjBIjEBbqZ0NKtBNzr8rtMHTOrLPeiwTkfUJZ3alg= +github.com/ldez/gomoddirectives v0.2.4/go.mod h1:oWu9i62VcQDYp9EQ0ONTfqLNh+mDLWWDO+SO0qSQw5g= +github.com/ldez/tagliatelle v0.5.0 h1:epgfuYt9v0CG3fms0pEgIMNPuFf/LpPIfjk4kyqSioo= +github.com/ldez/tagliatelle v0.5.0/go.mod h1:rj1HmWiL1MiKQuOONhd09iySTEkUuE/8+5jtPYz9xa4= +github.com/leonklingele/grouper v1.1.1 h1:suWXRU57D4/Enn6pXR0QVqqWWrnJ9Osrz+5rjt8ivzU= +github.com/leonklingele/grouper v1.1.1/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY= +github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= +github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= +github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM= +github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= +github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= +github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= +github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= +github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= +github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= +github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 h1:gWg6ZQ4JhDfJPqlo2srm/LN17lpybq15AryXIRcWYLE= +github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= +github.com/matoous/godox v0.0.0-20240105082147-c5b5e0e7c0c0 h1:Ny7cm4KSWceJLYyI1sm+aFIVDWSGXLcOJ0O0UaS5wdU= +github.com/matoous/godox v0.0.0-20240105082147-c5b5e0e7c0c0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +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.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwgOdMUQePUo= +github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= +github.com/mdempsky/unconvert v0.0.0-20230125054757-2661c2c99a9b h1:jdFI9paVi4E33U9TAExBpKPl1l5MnOn7VOLbb4Mvzzg= +github.com/mdempsky/unconvert v0.0.0-20230125054757-2661c2c99a9b/go.mod h1:mOq/NVYz3H5h7Av88ia14HIMF/UdGXj9dp8P/+b566A= +github.com/mdempsky/unconvert v0.0.0-20230907125504-415706980c06 h1:GC1BHRdynugzxNoEphewRqF4qcD/zzqQYsls4KXFtT8= +github.com/mdempsky/unconvert v0.0.0-20230907125504-415706980c06/go.mod h1:DuAZxNOBRkxMjbchCclLZxb/18Qb46cU26hBsomVuow= +github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 h1:zpIH83+oKzcpryru8ceC6BxnoG8TBrhgAvRg8obzup0= +github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= +github.com/mgechev/revive v1.2.4 h1:+2Hd/S8oO2H0Ikq2+egtNwQsVhAeELHjxjIUFX5ajLI= +github.com/mgechev/revive v1.2.4/go.mod h1:iAWlQishqCuj4yhV24FTnKSXGpbAA+0SckXB8GQMX/Q= +github.com/mgechev/revive v1.3.2 h1:Wb8NQKBaALBJ3xrrj4zpwJwqwNA6nDpyJSEQWcCka6U= +github.com/mgechev/revive v1.3.2/go.mod h1:UCLtc7o5vg5aXCwdUTU1kEBQ1v+YXPAkYDIDXbrs5I0= +github.com/mgechev/revive v1.5.0 h1:oaSmjA7rP8+HyoRuCgC531VHwnLH1AlJdjj+1AnQceQ= +github.com/mgechev/revive v1.5.0/go.mod h1:L6T3H8EoerRO86c7WuGpvohIUmiploGiyoYbtIWFmV8= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/moricho/tparallel v0.3.1 h1:fQKD4U1wRMAYNngDonW5XupoB/ZGJHdpzrWqgyg9krA= +github.com/moricho/tparallel v0.3.1/go.mod h1:leENX2cUv7Sv2qDgdi0D0fCftN8fRC67Bcn8pqzeYNI= +github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= +github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= +github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= +github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA= +github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= +github.com/nishanths/exhaustive v0.11.0 h1:T3I8nUGhl/Cwu5Z2hfc92l0e04D2GEW6e0l8pzda2l0= +github.com/nishanths/exhaustive v0.11.0/go.mod h1:RqwDsZ1xY0dNdqHho2z6X+bgzizwbLYOWnZbbl2wLB4= +github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= +github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= +github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= +github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= +github.com/nunnatsa/ginkgolinter v0.12.1 h1:vwOqb5Nu05OikTXqhvLdHCGcx5uthIYIl0t79UVrERQ= +github.com/nunnatsa/ginkgolinter v0.12.1/go.mod h1:AK8Ab1PypVrcGUusuKD8RDcl2KgsIwvNaaxAlyHSzso= +github.com/nunnatsa/ginkgolinter v0.18.2 h1:b2yZTU30JabiqGnmcJtFVXLBXyAj2Dmy7ZwPUbYphyc= +github.com/nunnatsa/ginkgolinter v0.18.2/go.mod h1:Mp5o0Yc8+cBJEmcu3QAzk6QxT0HgbWO7Ofb28zrJ/Vs= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= +github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/polyfloyd/go-errorlint v1.4.2 h1:CU+O4181IxFDdPH6t/HT7IiDj1I7zxNi1RIUxYwn8d0= +github.com/polyfloyd/go-errorlint v1.4.2/go.mod h1:k6fU/+fQe38ednoZS51T7gSIGQW1y94d6TkSr35OzH8= +github.com/polyfloyd/go-errorlint v1.6.0 h1:tftWV9DE7txiFzPpztTAwyoRLKNj9gpVm2cg8/OwcYY= +github.com/polyfloyd/go-errorlint v1.6.0/go.mod h1:HR7u8wuP1kb1NeN1zqTd1ZMlqUKPPHF+Id4vIPvDqVw= +github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= +github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/quasilyte/go-ruleguard v0.3.19 h1:tfMnabXle/HzOb5Xe9CUZYWXKfkS1KwRmZyPmD9nVcc= +github.com/quasilyte/go-ruleguard v0.3.19/go.mod h1:lHSn69Scl48I7Gt9cX3VrbsZYvYiBYszZOZW4A+oTEw= +github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 h1:+Wl/0aFp0hpuHM3H//KMft64WQ1yX9LdJY64Qm/gFCo= +github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= +github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= +github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= +github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= +github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= +github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= +github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= +github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= +github.com/raeperd/recvcheck v0.1.2 h1:SjdquRsRXJc26eSonWIo8b7IMtKD3OAT2Lb5G3ZX1+4= +github.com/raeperd/recvcheck v0.1.2/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryancurrah/gomodguard v1.3.0 h1:q15RT/pd6UggBXVBuLps8BXRvl5GPBcwVA7BJHMLuTw= +github.com/ryancurrah/gomodguard v1.3.0/go.mod h1:ggBxb3luypPEzqVtq33ee7YSN35V28XeGnid8dnni50= +github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV3oJmPU= +github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE= +github.com/ryanrolds/sqlclosecheck v0.4.0 h1:i8SX60Rppc1wRuyQjMciLqIzV3xnoHB7/tXbr6RGYNI= +github.com/ryanrolds/sqlclosecheck v0.4.0/go.mod h1:TBRRjzL31JONc9i4XMinicuo+s+E8yKZ5FN8X3G6CKQ= +github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= +github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= +github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= +github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc= +github.com/sanposhiho/wastedassign/v2 v2.0.7/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= +github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= +github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= +github.com/sashamelentyev/usestdlibvars v1.23.0 h1:01h+/2Kd+NblNItNeux0veSL5cBF1jbEOPrEhDzGYq0= +github.com/sashamelentyev/usestdlibvars v1.23.0/go.mod h1:YPwr/Y1LATzHI93CqoPUN/2BzGQ/6N/cl/KwgR0B/aU= +github.com/sashamelentyev/usestdlibvars v1.27.0 h1:t/3jZpSXtRPRf2xr0m63i32ZrusyurIGT9E5wAvXQnI= +github.com/sashamelentyev/usestdlibvars v1.27.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= +github.com/securego/gosec/v2 v2.14.0 h1:U1hfs0oBackChXA72plCYVA4cOlQ4gO+209dHiSNZbI= +github.com/securego/gosec/v2 v2.14.0/go.mod h1:Ff03zEi5NwSOfXj9nFpBfhbWTtROCkg9N+9goggrYn4= +github.com/securego/gosec/v2 v2.16.0 h1:Pi0JKoasQQ3NnoRao/ww/N/XdynIB9NRYYZT5CyOs5U= +github.com/securego/gosec/v2 v2.16.0/go.mod h1:xvLcVZqUfo4aAQu56TNv7/Ltz6emAOQAEsrZrt7uGlI= +github.com/securego/gosec/v2 v2.21.4 h1:Le8MSj0PDmOnHJgUATjD96PaXRvCpKC+DGJvwyy0Mlk= +github.com/securego/gosec/v2 v2.21.4/go.mod h1:Jtb/MwRQfRxCXyCm1rfM1BEiiiTfUOdyzzAhlr6lUTA= +github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= +github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= +github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= +github.com/sivchari/nosnakecase v1.7.0 h1:7QkpWIRMe8x25gckkFd2A5Pi6Ymo0qgr4JrhGt95do8= +github.com/sivchari/nosnakecase v1.7.0/go.mod h1:CwDzrzPea40/GB6uynrNLiorAlgFRvRbFSgJx2Gs+QY= +github.com/sivchari/tenv v1.7.1 h1:PSpuD4bu6fSmtWMxSGWcvqUUgIn7k3yOJhOIzVWn8Ak= +github.com/sivchari/tenv v1.7.1/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg= +github.com/sivchari/tenv v1.12.1 h1:+E0QzjktdnExv/wwsnnyk4oqZBUfuh89YMQT1cyuvSY= +github.com/sivchari/tenv v1.12.1/go.mod h1:1LjSOUCc25snIr5n3DtGGrENhX3LuWefcplwVGC24mw= +github.com/sonatard/noctx v0.0.2 h1:L7Dz4De2zDQhW8S0t+KUjY0MAQJd6SgVwhzNIc4ok00= +github.com/sonatard/noctx v0.0.2/go.mod h1:kzFz+CzWSjQ2OzIm46uJZoXuBpa2+0y3T36U18dWqIo= +github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM= +github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= +github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= +github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= +github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0= +github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= +github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= +github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= +github.com/stbenjam/no-sprintf-host-port v0.1.1 h1:tYugd/yrm1O0dV+ThCbaKZh195Dfm07ysF0U6JQXczc= +github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8LHsN9N74I+PhRquPsxpL0I= +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 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/swaggo/swag v1.8.9 h1:kHtaBe/Ob9AZzAANfcn5c6RyCke9gG9QpH0jky0I/sA= +github.com/swaggo/swag v1.8.9/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk= +github.com/swaggo/swag v1.16.1 h1:fTNRhKstPKxcnoKsytm4sahr8FaYzUcT7i1/3nd/fBg= +github.com/swaggo/swag v1.16.1/go.mod h1:9/LMvHycG3NFHfR6LwvikHv5iFvmPADQ359cKikGxto= +github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9qkHM= +github.com/tdakkota/asciicheck v0.2.0/go.mod h1:Qb7Y9EgjCLJGup51gDHFzbI08/gbGhL/UVhYIPWG2rg= +github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= +github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= +github.com/tetafro/godot v1.4.11 h1:BVoBIqAf/2QdbFmSwAWnaIqDivZdOV0ZRwEm6jivLKw= +github.com/tetafro/godot v1.4.11/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8= +github.com/tetafro/godot v1.4.18 h1:ouX3XGiziKDypbpXqShBfnNLTSjR8r3/HVzrtJ+bHlI= +github.com/tetafro/godot v1.4.18/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= +github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+nhpFa4gg4yJyTRJ13reZMDHrKwYw53M= +github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= +github.com/timakin/bodyclose v0.0.0-20241017074824-adbc21e6bf36 h1:BLrrwIAzisfgAzwJXJmDV13xxgP8S0ITQtc8vVFPRXY= +github.com/timakin/bodyclose v0.0.0-20241017074824-adbc21e6bf36/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= +github.com/timonwong/loggercheck v0.9.4 h1:HKKhqrjcVj8sxL7K77beXh0adEm6DLjV/QOGeMXEVi4= +github.com/timonwong/loggercheck v0.9.4/go.mod h1:caz4zlPcgvpEkXgVnAJGowHAMW2NwHaNlpS8xDbVhTg= +github.com/timonwong/loggercheck v0.10.1 h1:uVZYClxQFpw55eh+PIoqM7uAOHMrhVcDoWDery9R8Lg= +github.com/timonwong/loggercheck v0.10.1/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= +github.com/tomarrell/wrapcheck/v2 v2.8.1 h1:HxSqDSN0sAt0yJYsrcYVoEeyM4aI9yAm3KQpIXDJRhQ= +github.com/tomarrell/wrapcheck/v2 v2.8.1/go.mod h1:/n2Q3NZ4XFT50ho6Hbxg+RV1uyo2Uow/Vdm9NQcl5SE= +github.com/tomarrell/wrapcheck/v2 v2.9.0 h1:801U2YCAjLhdN8zhZ/7tdjB3EnAoRlJHt/s+9hijLQ4= +github.com/tomarrell/wrapcheck/v2 v2.9.0/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= +github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= +github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= +github.com/ultraware/funlen v0.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iLA= +github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= +github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI= +github.com/ultraware/funlen v0.1.0/go.mod h1:XJqmOQja6DpxarLj6Jj1U7JuoS8PvL4nEqDaQhy22p4= +github.com/ultraware/whitespace v0.0.5 h1:hh+/cpIcopyMYbZNVov9iSxvJU3OYQg78Sfaqzi/CzI= +github.com/ultraware/whitespace v0.0.5/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= +github.com/ultraware/whitespace v0.1.1 h1:bTPOGejYFulW3PkcrqkeQwOd6NKOOXvmGD9bo/Gk8VQ= +github.com/ultraware/whitespace v0.1.1/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/uudashr/gocognit v1.0.6 h1:2Cgi6MweCsdB6kpcVQp7EW4U23iBFQWfTXiWlyp842Y= +github.com/uudashr/gocognit v1.0.6/go.mod h1:nAIUuVBnYU7pcninia3BHOvQkpQCeO76Uscky5BOwcY= +github.com/uudashr/gocognit v1.1.3 h1:l+a111VcDbKfynh+airAy/DJQKaXh2m9vkoysMPSZyM= +github.com/uudashr/gocognit v1.1.3/go.mod h1:aKH8/e8xbTRBwjbCkwZ8qt4l2EpKXl31KMHgSS+lZ2U= +github.com/uudashr/iface v1.2.0 h1:ECJjh5q/1Zmnv/2yFpWV6H3oMg5+Mo+vL0aqw9Gjazo= +github.com/uudashr/iface v1.2.0/go.mod h1:Ux/7d/rAF3owK4m53cTVXL4YoVHKNqnoOeQHn2xrlp0= +github.com/xen0n/gosmopolitan v1.2.1 h1:3pttnTuFumELBRSh+KQs1zcz4fN6Zy7aB0xlnQSn1Iw= +github.com/xen0n/gosmopolitan v1.2.1/go.mod h1:JsHq/Brs1o050OOdmzHeOr0N7OtlnKRAGAsElF8xBQA= +github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= +github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= +github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= +github.com/yeya24/promlinter v0.2.0 h1:xFKDQ82orCU5jQujdaD8stOHiv8UN68BSdn2a8u8Y3o= +github.com/yeya24/promlinter v0.2.0/go.mod h1:u54lkmBOZrpEbQQ6gox2zWKKLKu2SGe+2KOiextY+IA= +github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= +github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4= +github.com/ykadowak/zerologlint v0.1.2 h1:Um4P5RMmelfjQqQJKtE8ZW+dLZrXrENeIzWWKw800U4= +github.com/ykadowak/zerologlint v0.1.2/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= +github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= +github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +gitlab.com/bosi/decorder v0.2.3 h1:gX4/RgK16ijY8V+BRQHAySfQAb354T7/xQpDB2n10P0= +gitlab.com/bosi/decorder v0.2.3/go.mod h1:9K1RB5+VPNQYtXtTDAzd2OEftsZb1oV0IrJrzChSdGE= +gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= +gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= +go-simpler.org/musttag v0.13.0 h1:Q/YAW0AHvaoaIbsPj3bvEI5/QFP7w696IMUpnKXQfCE= +go-simpler.org/musttag v0.13.0/go.mod h1:FTzIGeK6OkKlUDVpj0iQUXZLUO1Js9+mvykDQy9C5yM= +go-simpler.org/sloglint v0.7.2 h1:Wc9Em/Zeuu7JYpl+oKoYOsQSy2X560aVueCW/m6IijY= +go-simpler.org/sloglint v0.7.2/go.mod h1:US+9C80ppl7VsThQclkM7BkCHQAzuz8kHLsW3ppuluo= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0 h1:qtFISDHKolvIxzSs0gIaiPUPR0Cucb0F2coHC7ZLdps= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0/go.mod h1:Y+Pop1Q6hCOnETWTW4NROK/q1hv50hM7yDaUTjG8lp8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= +go.tmz.dev/musttag v0.7.0 h1:QfytzjTWGXZmChoX0L++7uQN+yRCPfyFm+whsM+lfGc= +go.tmz.dev/musttag v0.7.0/go.mod h1:oTFPvgOkJmp5kYL02S8+jrH0eLrBIl57rzWeA26zDEM= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +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/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4= +golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= +golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a h1:Jw5wfR+h9mnIYH+OtGT2im5wV1YGGDora5vTv/aa5bE= +golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2 h1:J74nGeMgeFnYQJN59eFwh06jX/V8g0lB7LWpjSLxtgU= +golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f h1:WTyX8eCCyfdqiPYkRGm0MqElSfYFH3yR1+rl/mct9sA= +golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= +golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/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 h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/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.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190321232350-e250d351ecad/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= +golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.205.0 h1:LFaxkAIpDb/GsrWV20dMMo5MR0h8UARTbn24LmD+0Pg= +google.golang.org/api v0.205.0/go.mod h1:NrK1EMqO8Xk6l6QwRAmrXXg2v6dzukhlOyvkYtnvUuc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= +google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.4.3 h1:o/n5/K5gXqk8Gozvs2cnL0F2S1/g1vcGCAx2vETjITw= +honnef.co/go/tools v0.4.3/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA= +honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I= +honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= +mvdan.cc/gofumpt v0.4.0 h1:JVf4NN1mIpHogBj7ABpgOyZc65/UUOkKQFkoURsz4MM= +mvdan.cc/gofumpt v0.4.0/go.mod h1:PljLOHDeZqgS8opHRKLzp2It2VBuSdteAgqUfzMTxlQ= +mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E= +mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js= +mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= +mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= +mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d h1:3rvTIIM22r9pvXk+q3swxUQAQOxksVMGK7sml4nG57w= +mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d/go.mod h1:IeHQjmn6TOD+e4Z3RFiZMMsLVL+A96Nvptar8Fj71is= +mvdan.cc/unparam v0.0.0-20240917084806-57a3b4290ba3 h1:YkmTN1n5U60NM02j7TCSWRlW3fqNiuXe/eVXf0dLFN8= +mvdan.cc/unparam v0.0.0-20240917084806-57a3b4290ba3/go.mod h1:z5yboO1sP1Q9pcfvS597TpfbNXQjphDlkCJHzt13ybc= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/tools/check/golangci-from-gitea.yml b/tools/check/golangci-from-gitea.yml new file mode 100644 index 0000000..966ea2d --- /dev/null +++ b/tools/check/golangci-from-gitea.yml @@ -0,0 +1,116 @@ +linters: + enable: + - gosimple + - deadcode + - typecheck + - govet + - errcheck + - staticcheck + - unused + - structcheck + - varcheck + - golint + - dupl + - gocyclo # The cyclomatic complexety of a lot of functions is too high, we should refactor those another time. + - gofmt + - misspell + - gocritic + enable-all: false + disable-all: true + fast: false + +run: + timeout: 3m + +linters-settings: + gocritic: + disabled-checks: + - ifElseChain + - singleCaseSwitch # Every time this occurred in the code, there was no other way. + +issues: + exclude-rules: + # Exclude some linters from running on tests files. + - path: _test\.go + linters: + - gocyclo + - errcheck + - dupl + - gosec + - unparam + - staticcheck + - path: models/migrations/v + linters: + - gocyclo + - errcheck + - dupl + - gosec + - linters: + - dupl + text: "webhook" + - linters: + - gocritic + text: "`ID' should not be capitalized" + - path: modules/templates/helper.go + linters: + - gocritic + - linters: + - unused + - deadcode + text: "swagger" + - path: contrib/pr/checkout.go + linters: + - errcheck + - path: models/issue.go + linters: + - errcheck + - path: models/migrations/ + linters: + - errcheck + - path: modules/log/ + linters: + - errcheck + - path: routers/routes/web.go + linters: + - dupl + - path: routers/api/v1/repo/issue_subscription.go + linters: + - dupl + - path: routers/repo/view.go + linters: + - dupl + - path: models/migrations/ + linters: + - unused + - linters: + - staticcheck + text: "argument x is overwritten before first use" + - path: modules/httplib/httplib.go + linters: + - staticcheck + # Enabling this would require refactoring the methods and how they are called. + - path: models/issue_comment_list.go + linters: + - dupl + - linters: + - misspell + text: '`Unknwon` is a misspelling of `Unknown`' + - path: models/update.go + linters: + - unused + - path: cmd/dump.go + linters: + - dupl + - path: services/webhook/webhook.go + linters: + - structcheck + - text: "commentFormatting: put a space between `//` and comment text" + linters: + - gocritic + - text: "exitAfterDefer:" + linters: + - gocritic + - path: modules/graceful/manager_windows.go + linters: + - staticcheck + text: "svc.IsAnInteractiveSession is deprecated: Use IsWindowsService instead." diff --git a/tools/check/golangci.yml b/tools/check/golangci.yml new file mode 100644 index 0000000..678931f --- /dev/null +++ b/tools/check/golangci.yml @@ -0,0 +1,83 @@ +linters-settings: + funlen: + lines: 90 + varnamelen: + max-distance: 25 + ignore-names: + - id + - ip + - i + - db + - ok + - to + - b + ignore-decls: + v1 *gin.RouterGroup + #errcheck: + # path to a file containing a list of functions to exclude from checking + # see https://github.com/kisielk/errcheck#excluding-functions for details + #exclude-functions: tools/check/errcheck_excludes.txt + dupl: + threshold: 100 + goconst: + min-len: 2 + min-occurrences: 7 + misspell: + locale: US + ignore-words: + - cancelled + - marshalling + + +linters: + enable-all: true + disable: + - errcheck # Revive already check this and his exclude list works well + - gomoddirectives + - prealloc + - godot + - godox + - unused + - gci # Already checked golangci semms to be using an old version + - forbidigo # We are using the fmt Print functions in scripts + - exhaustruct + - nonamedreturns # Sometimes named returns are useful as they serve as documentation + - rowserrcheck # is disabled because of generics + - sqlclosecheck # is disabled because of generics + - wastedassign # is disabled because of generics + - contextcheck # Causes timeout + - exhaustive # Causes timeout + - gosimple # Causes timeout + - govet # Causes timeout + - staticcheck # Causes timeout + - gocritic + - tagalign + - depguard + - musttag + - err113 + - interfacebloat + - lll # revive linter is already checking this + - gocyclo # revive linter is already checking this + - cyclop # revive linter is already checking this + - gocognit # revive linter is already checking this + - maintidx # revive linter is already checking this + - gosec # gosec linter is already checking this + - gochecknoinits + - wsl # check style only + - whitespace # check whitespaces only + - exportloopref # Deprecated + - mnd + +run: + issues.exclude-dirs: + - data + - out + - doc + - snap + - vendor + +issues: + exclude-rules: + - text: "weak cryptographic primitive" + linters: + - gosec diff --git a/tools/check/revive.toml b/tools/check/revive.toml new file mode 100644 index 0000000..7a677d4 --- /dev/null +++ b/tools/check/revive.toml @@ -0,0 +1,75 @@ +# See this page for descriptions: +# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md + +# got from here: https://github.com/pyroscope-io/pyroscope/blob/main/revive.toml + +ignoreGeneratedHeader = false +severity = "error" +confidence = 0.8 +errorCode = -1 +warningCode = -1 + +[directive.specify-disable-reason] +[rule.context-keys-type] +[rule.time-naming] +[rule.var-declaration] +[rule.unexported-return] +[rule.errorf] +[rule.blank-imports] +[rule.context-as-argument] +[rule.dot-imports] +[rule.error-return] +[rule.error-strings] +[rule.error-naming] +#[rule.exported] +[rule.if-return] +[rule.increment-decrement] +[rule.var-naming] +[rule.package-comments] +[rule.range] +[rule.receiver-naming] +[rule.indent-error-flow] +[rule.argument-limit] +arguments = [5] +[rule.cyclomatic] +arguments = [10] +[rule.empty-block] +[rule.superfluous-else] +[rule.confusing-naming] +[rule.get-return] +[rule.modifies-parameter] +[rule.confusing-results] +[rule.deep-exit] +[rule.unused-parameter] +[rule.unreachable-code] +#[rule.add-constant] +arguments = [{ maxLitCount = "3", allowStrs = "\"\"", allowInts = "0,1,2,3,4,5,6,7,8,9,10,16,24,32,40,48,56,64,128,256,512,0xff,1000,0o666,0o700", allowFloats = "0.0,0.,1.0,1.,2.0,2." }] +[rule.flag-parameter] +[rule.unnecessary-stmt] +[rule.struct-tag] +[rule.modifies-value-receiver] +[rule.constant-logical-expr] +[rule.bool-literal-in-expr] +[rule.redefines-builtin-id] +[rule.function-result-limit] +arguments = [4] +[rule.imports-blacklist] +[rule.range-val-in-closure] +[rule.range-val-address] +[rule.waitgroup-by-value] +[rule.atomic] +[rule.empty-lines] +[rule.line-length-limit] +arguments = [160] +[rule.call-to-gc] +[rule.duplicated-imports] +[rule.import-shadowing] +[rule.bare-return] +[rule.unused-receiver] +# we already have errcheck in place to do this check and ignore rules works better +#[rule.unhandled-error] +#arguments = ["sb.WriteString", "fmt.Printf", "fmt.Println", "fmt.Print"] +severity = "error" +[rule.cognitive-complexity] +arguments = [15] +[rule.string-of-int] diff --git a/tools/deploy-with-ssh-key.sh b/tools/deploy-with-ssh-key.sh new file mode 100755 index 0000000..b55e5ba --- /dev/null +++ b/tools/deploy-with-ssh-key.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +# Check environment to build +ENV=$1 +CUSTOM_ENV=$2 + +# Checking valid environment +if [ "$ENV" != "prod" ] && [ "$ENV" != "dev" ] && [ "$ENV" != "demo" ] && [ "$ENV" != "open-demo" ]; then + if [ "$CUSTOM_ENV" != "true" ]; then + echo "Invalid environment $ENV"; + exit 0; + fi + echo "WARNING: you are using a custom env $CUSTOM_ENV" +fi + +ssh quantex@"$ENV".quantex.com.ar "mkdir -p qfixpt" +scp build/out/distribution/qfixpt.gz quantex@"$ENV".quantex.com.ar:./qfixpt/ +ssh quantex@"$ENV".quantex.com.ar "unzip_qfixpt.sh" diff --git a/tools/deploy.sh b/tools/deploy.sh new file mode 100755 index 0000000..4af0679 --- /dev/null +++ b/tools/deploy.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +while getopts "a:" opt; do + case $opt in + a) + ssh_alias=$OPTARG + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + exit 1 + ;; + esac +done + +# Check if the alias is not empty +if [ -z "$ssh_alias" ]; then + echo "\033[31mPlease input an ssh alias. Usage: deploy.sh -a {ssh_alias}" + exit 1 +fi + +# Check if the build exists +if [ ! -f "build/out/distribution/qfixpt.gz" ]; then + echo "\033[31mFile build/out/distribution/qfixpt.gz does not exist. Run make build" + exit 1 +fi + +# Copy zipped binary +scp build/out/distribution/qfixpt.gz "$ssh_alias":/home/quantex/qfixpt/ + +# Unzip the binary. Then systemd restart it automatically on file change. +ssh "$ssh_alias" "chown quantex:quantex -R /home/quantex/qfixpt;chmod +x /home/quantex/qfixpt/unzip-qfixpt.sh; /home/quantex/qfixpt/unzip-qfixpt.sh" + + diff --git a/tools/deploy_new_linters.sh b/tools/deploy_new_linters.sh new file mode 100644 index 0000000..624b0df --- /dev/null +++ b/tools/deploy_new_linters.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +qscp linter_version.txt quantex@app.quantex.com.ar:~/linter_version.txt \ No newline at end of file diff --git a/tools/first_deploy.sh b/tools/first_deploy.sh new file mode 100755 index 0000000..a1bfebe --- /dev/null +++ b/tools/first_deploy.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +while getopts "a:" opt; do + case $opt in + a) + ssh_alias=$OPTARG + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + exit 1 + ;; + esac +done + +# Check if the alias is not empty +if [ -z "$ssh_alias" ]; then + echo "\033[31mPlease input an ssh alias. Usage: first_deploy.sh -a {ssh_alias}" + exit 1 +fi + +# Check if the build exists +if [ ! -f "build/out/distribution/qfixpt.gz" ]; then + echo "\033[31mFile build/out/distribution/qfixpt.gz does not exist. Run make build" + exit 1 +fi + +# Create the service +scp tools/qfixpt.service "$ssh_alias":/lib/systemd/system/ + +# Create folder just in case +ssh "$ssh_alias" "systemctl daemon-reload;systemctl enable qfixpt.service;systemctl start qfixpt.service;mkdir -p /home/quantex/qfixpt" + +# Create start, stop and restart scripts +scp tools/start "$ssh_alias":/home/quantex/qfixpt/ +scp tools/stop "$ssh_alias":/home/quantex/qfixpt/ +scp tools/restart "$ssh_alias":/home/quantex/qfixpt/ + +# Copy zipped binary +scp build/out/distribution/qfixpt.gz "$ssh_alias":/home/quantex/qfixpt/ +scp tools/unzip-qfixpt.sh "$ssh_alias":/home/quantex/qfixpt/ + +# Unzip the binary. Then systemd restart it automatically on file change. +ssh "$ssh_alias" "chown quantex:quantex -R /home/quantex/qfixpt;chmod +x /home/quantex/qfixpt/unzip-qfixpt.sh; /home/quantex/qfixpt/unzip-qfixpt.sh" diff --git a/tools/generate-jwt.sh b/tools/generate-jwt.sh new file mode 100755 index 0000000..2ae449e --- /dev/null +++ b/tools/generate-jwt.sh @@ -0,0 +1,110 @@ +#!/bin/bash + +set -e + +read -r -p "Issuer: " ISSUER +read -r -p "Service (e.g. SKELETON): " SERVICE +read -r -p "Token: " TOKEN +read -r -p "Expire (e.g. 24h) [none]: " EXPIRY + +if [ -z "$EXPIRY" ]; then + EXPIRY="none" +fi + +# Check if secret key is set +SERVICE_UPPER=$(printf "%s" "$SERVICE" | tr '[:lower:]' '[:upper:]') +SECRET_KEY_VAR="${SERVICE_UPPER}_QUANTEX_SECRET_KEY" +if [ -z "${!SECRET_KEY_VAR}" ]; then + echo "Error: Environment variable $SECRET_KEY_VAR is not set" >&2 + echo "" >&2 + echo "Please set the secret key:" >&2 + echo " export $SECRET_KEY_VAR=\"your-secret-key\"" >&2 + exit 1 +fi + +# Create temporary directory +TEMP_DIR=$(mktemp -d) +trap "rm -rf $TEMP_DIR" EXIT + +# Create Go program +cat > "$TEMP_DIR/main.go" << 'GOCODE' +package main + +import ( + "fmt" + "os" + "strings" + "time" + + "github.com/golang-jwt/jwt" +) + +func main() { + if len(os.Args) != 5 { + fmt.Fprintf(os.Stderr, "Usage: %s \n", os.Args[0]) + os.Exit(1) + } + + token := os.Args[1] + issuer := os.Args[2] + expiryStr := os.Args[3] + secret := os.Args[4] + + now := time.Now() + claims := jwt.MapClaims{ + "token": token, + "permissions": []string{"FullAccess"}, + "iss": issuer, + "iat": now.Unix(), + } + + if strings.ToLower(expiryStr) != "none" && expiryStr != "-1" { + duration, err := time.ParseDuration(expiryStr) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: Invalid expiry duration '%s': %v\n", expiryStr, err) + fmt.Fprintf(os.Stderr, "Use format like: 1h, 24h, 7d, 168h\n") + os.Exit(1) + } + claims["exp"] = now.Add(duration).Unix() + } + + // Create token + jwttoken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + // Sign token + signedToken, err := jwttoken.SignedString([]byte(secret)) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: Failed to sign token: %v\n", err) + os.Exit(1) + } + + // Output token + fmt.Println(signedToken) +} +GOCODE + +# Initialize go.mod in temp directory +cd "$TEMP_DIR" +cat > go.mod << GOMOD +module jwt-generator + +go 1.24 + +require github.com/golang-jwt/jwt v3.2.2+incompatible +GOMOD + +go mod download > /dev/null 2>&1 + +# Build the Go program +go build -o jwt-gen main.go 2>&1 | grep -v "go: downloading" || true + +# Run the Go program +JWT_TOKEN=$(./jwt-gen "$TOKEN" "$ISSUER" "$EXPIRY" "${!SECRET_KEY_VAR}") + +if [ $? -ne 0 ]; then + echo "Error: Failed to generate JWT token" >&2 + exit 1 +fi + +# Output the token +echo "$JWT_TOKEN" diff --git a/tools/global_conf_example.toml b/tools/global_conf_example.toml new file mode 100644 index 0000000..d8d3ce0 --- /dev/null +++ b/tools/global_conf_example.toml @@ -0,0 +1,28 @@ +CertEncryptionKey = "" +QApixToken = "" +SlackSysMonitor = "" +BYMAQBrokerID = "" +GitUser = "" +GitPass = "" +QRouterURL = "http://localhost:9050" +QApixURL = "http://localhost:5001" + +MultiDB.FolderName = "multiDBLogs" +MultiDB.MultiDBMode = "OnlyMaster" # All | OnlyMaster | MasterAndSlave | MasterAndImmudb + +[MQTT] +Protocol = "wss" +URL = "async-non-prod.quantex.com.ar/mqtt/" +Subdomain = "dev" +Secret = "" + +[MultiDB.MasterDataBase] +Insecure = false +CACrt = "../certs/cockroachdb/ca.crt" +ClientCrt = "../certs/cockroachdb/client.quantex.crt" +ClientCrtKey = "../certs/cockroachdb/client.quantex.key" +User = "" +Host = "" +Port = "26257" +Database = "quantex" + diff --git a/tools/print-version.sh b/tools/print-version.sh new file mode 100755 index 0000000..6749fc8 --- /dev/null +++ b/tools/print-version.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +# This allow to set the default path, if script used outside makefile +if [ -z "$OUT_PATH" ]; then + OUT_PATH="./build/out/distribution" +fi + +# if no environment argument, set to default value dev +if [ -z "$1" ]; then + ENV="dev" +else + ENV="$1" +fi + +if COMMIT_MSG=$(QUANTEX_ENVIRONMENT=$ENV "${OUT_PATH}/qfixpt" -v 2>/dev/null); then + echo "$COMMIT_MSG" +else + echo "Unable to get qfixpt version." + file "${OUT_PATH}/qfixpt" +fi + diff --git a/tools/push-script.sh b/tools/push-script.sh new file mode 100755 index 0000000..9f5ac9f --- /dev/null +++ b/tools/push-script.sh @@ -0,0 +1,65 @@ +# This scripts copy the start service script into the server. +# You need a ssh key with root permissions. + +ENV=$1 +CUSTOM_ENV=$2 + +# Checking valid environment +if [ "$ENV" != "prod" ] && [ "$ENV" != "dev" ] && [ "$ENV" != "demo" ] && [ "$ENV" != "open-demo" ]; then + if [ "$CUSTOM_ENV" != "true" ]; then + echo "Invalid environment $ENV"; + exit 0; + fi + echo "WARNING: you are using a custom env $CUSTOM_ENV" +fi + +echo "\033[0;31m" +echo " *************************************************************************" +echo " | |" +echo " | >>> Make sure you already created an user \033[0;45m quantex \033[0m\033[0;31m |" +echo " | on the $ENV server!!! |" +echo " | |" +echo " | Use: adduser --shell /bin/rbash --home /home/quantex quantex |" +echo " | |" +echo " | >>> and add \033[0;45m QUANTEX_PASS \033[0m\033[0;31m variable on GitLab |" +echo " | |" +echo " *************************************************************************" +echo "\033[0m" + +read -p "Press enter to continue" + +echo "Pushing scripts to $ENV.quantex.com.ar" + +ssh root@"$ENV".quantex.com.ar "mkdir -p /home/quantex/qfixpt; chown quantex:quantex /home/quantex/qfixpt" +ssh root@"$ENV".quantex.com.ar "mkdir -p /home/quantex/qfixpt/logs; chown quantex:quantex /home/quantex/qfixpt/logs" + +scp tools/unzip-qfixpt.sh root@"$ENV".quantex.com.ar:/usr/bin/ +ssh root@"$ENV".quantex.com.ar "chmod +x /usr/bin/unzip-qfixpt.sh" + +scp tools/start root@"$ENV".quantex.com.ar:/home/quantex/qfixpt/ +ssh root@"$ENV".quantex.com.ar "chmod +x /home/quantex/qfixpt/start; chown quantex:quantex /home/quantex/qfixpt/start" + +scp tools/stop root@"$ENV".quantex.com.ar:/home/quantex/qfixpt/ +ssh root@"$ENV".quantex.com.ar "chmod +x /home/quantex/qfixpt/stop; chown quantex:quantex /home/quantex/qfixpt/stop" + +scp tools/backup.sh root@"$ENV".quantex.com.ar:/home/quantex/qfixpt/ +ssh root@"$ENV".quantex.com.ar "chmod +x /home/quantex/qfixpt/backup.sh; chown quantex:quantex /home/quantex/qfixpt/backup.sh" + +scp tools/rollback.sh root@"$ENV".quantex.com.ar:/home/quantex/qfixpt/ +ssh root@"$ENV".quantex.com.ar "chmod +x /home/quantex/qfixpt/rollback.sh; chown quantex:quantex /home/quantex/qfixpt/rollback.sh" + +scp tools/qfixpt.service root@"$ENV".quantex.com.ar:/etc/systemd/system/ +scp tools/qfixpt-watcher.path root@"$ENV".quantex.com.ar:/etc/systemd/system/ +scp tools/qfixpt-watcher.service root@"$ENV".quantex.com.ar:/etc/systemd/system/ + +ssh root@"$ENV".quantex.com.ar "systemctl enable qfixpt.service; systemctl start qfixpt.service" +ssh root@"$ENV".quantex.com.ar "systemctl enable qfixpt-watcher.path; systemctl start qfixpt-watcher.path" + +echo "\033[0;31m" +echo " ************************************************" +echo " | |" +echo " | >>> Remember you need a \033[0;45m conf.toml \033[0m\033[0;31m file |" +echo " | on the qfixpt folder!!! |" +echo " | |" +echo " ************************************************\033[0m" + diff --git a/tools/qfixpt-watcher.path b/tools/qfixpt-watcher.path new file mode 100644 index 0000000..8aa5ecb --- /dev/null +++ b/tools/qfixpt-watcher.path @@ -0,0 +1,8 @@ +[Path] +PathModified=/home/quantex/qfixpt/qfixpt + +[Install] +WantedBy=multi-user.target + +# See: https://superuser.com/questions/1171751/restart-systemd-service-automatically-whenever-a-directory-changes-any-file-ins +# https://zerokspot.com/weblog/2018/09/15/executing-jobs-on-filechanges-with-systemd/ diff --git a/tools/qfixpt-watcher.service b/tools/qfixpt-watcher.service new file mode 100644 index 0000000..6838144 --- /dev/null +++ b/tools/qfixpt-watcher.service @@ -0,0 +1,10 @@ +[Unit] +Description=QFIXPT restarter +After=network.target + +[Service] +Type=oneshot +ExecStart=/bin/systemctl restart qfixpt.service + +[Install] +WantedBy=multi-user.target diff --git a/tools/qfixpt.service b/tools/qfixpt.service new file mode 100644 index 0000000..d7dae85 --- /dev/null +++ b/tools/qfixpt.service @@ -0,0 +1,19 @@ +[Unit] +Description=QFIXPT Service +#Requires=network.target +After=network.target + +[Service] +Type=simple +WorkingDirectory=/home/quantex/qfixpt +ExecStart=/home/quantex/qfixpt/qfixpt run try-api --globalCfg=/home/quantex/global_conf.toml +TimeoutStopSec=60 +Restart=always +RestartSec=10 +StandardOutput=append:/home/quantex/qfixpt/logs/std_out_and_err.log +StandardError=append:/home/quantex/qfixpt/logs/std_out_and_err.log +User=quantex +Group=quantex + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/tools/remove_multidb.sh b/tools/remove_multidb.sh new file mode 100755 index 0000000..40f2418 --- /dev/null +++ b/tools/remove_multidb.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +set -e + +MODULE_IMPORT="quantex.com.ar/multidb" +MODULE_NAME="multidb" +DB_VAR_NAME="database" + +echo "Removing all references to '$MODULE_NAME' and replacing with 'database/sql' where needed..." + +FILES=$(find ./src -type f -name "*.go") + +for file in $FILES; do + echo "Processing $file" + + # 1. Remove explicit import by module path + perl -pi -e "s|.*$MODULE_IMPORT.*\n||g" "$file" + + # 2. Remove any import line containing 'multidb' + perl -pi -e 's|.*"[^"]*multidb[^"]*".*\n||g' "$file" + + # 3. Replace multidb.New(...) with sql.Open(...) + perl -pi -e "s|$MODULE_NAME\.New\s*\(\s*serviceName:\s*\"[^\"]*\",\s*(.*?)\)|sql.Open(\"postgres\", \1)|g" "$file" + + # 4. Comment out database.Start() + perl -pi -e "s|$DB_VAR_NAME\.Start\(\)|// $DB_VAR_NAME.Start() removed, not needed in database/sql|g" "$file" + + # 5. Remove any line containing .PeriodicDBPing + perl -pi -e "s|.*\.PeriodicDBPing.*\n||g" "$file" + + # 6. Remove any reference to *multidb.Type + perl -pi -e "s|\*?$MODULE_NAME\.[A-Za-z0-9_]+||g" "$file" + + # 7. Remove the field 'MultiDB multidb.Config' from structs + perl -pi -e "s|MultiDB\s+$MODULE_NAME\.Config\s*||g" "$file" + + # 8. Remove orphan field 'MultiDB' with no type + perl -pi -e 's/^\s*MultiDB\s*\n//g' "$file" + + # 9. Remove field 'db' with no type (after type removal) + perl -pi -e "s|^\s*db\s*\n||g" "$file" + + # 10. Replace store.Config{MultiDB: ...} with store.Config{} + perl -pi -e 's|store\.Config\s*{\s*MultiDB:\s*[^}]+}|store.Config{}|g' "$file" + + # 11. Remove p.db.Close() line + perl -pi -e 's|^[ \t]*p\.db\.Close\(\)[ \t]*;?[ \t]*\n||g' "$file" + + # 12. Remove block from database initialization to return s, nil + perl -0777 -pi -e 's|database\s*,\s*err\s*:=.*?\n.*?return s,\s*nil\n||gs' "$file" + + # 13. If function New(...) is now empty, insert 'return nil, nil' + perl -0777 -pi -e 's|func\s+New\([^\)]*\)\s*\(\*Store,\s*error\)\s*{\s*}|func New(config Config) (*Store, error) {\n return nil, nil\n}|g' "$file" + + # 14. Clean Store struct, leave it empty if db field was removed + perl -0777 -pi -e 's|type Store struct\s*{\s*db\s*:.*?\n\s*}|type Store struct {}|g' "$file" +done + +# 15. Remove import "database/sql" if not used anymore +for file in $FILES; do + if ! grep -q "sql." "$file" && grep -q '"database/sql"' "$file"; then + echo "Removing unused import 'database/sql' in $file" + perl -pi -e 's|.*"database/sql".*\n||g' "$file" + fi +done + +# 16. Remove the module requirement from go.mod and clean go.sum +echo "Cleaning go.mod and go.sum..." +go mod edit -droprequire=$MODULE_IMPORT || true +go mod tidy +go mod vendor || true + +# 17. Remove vendored copy if exists +rm -rf ./vendor/quantex.com.ar/multidb || true + +echo "multidb module fully removed." \ No newline at end of file diff --git a/tools/restart b/tools/restart new file mode 100644 index 0000000..052ecf0 --- /dev/null +++ b/tools/restart @@ -0,0 +1,4 @@ +#!/bin/bash + +systemctl daemon-reload +systemctl restart skeleton.service \ No newline at end of file diff --git a/tools/rollback.sh b/tools/rollback.sh new file mode 100644 index 0000000..1dfe166 --- /dev/null +++ b/tools/rollback.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +mv qfixpt qfixpt_tmp + +if [ -f backups/qfixpt.gz ]; then + mv backups/qfixpt.gz . + gzip -fkd qfixpt.gz +else + mv qfixpt_tmp qfixpt + echo "Error there is no backup file qfixpt.gz" +fi + +mv conf.toml conf.toml_tmp + +if [ -f backups/conf.toml ]; then + mv backups/conf.toml . +else + mv conf.toml_tmp conf.toml + echo "Error there is no config backup file conf.toml" +fi diff --git a/tools/start b/tools/start new file mode 100644 index 0000000..7ba6b6d --- /dev/null +++ b/tools/start @@ -0,0 +1,4 @@ +#!/bin/bash + +systemctl daemon-reload +systemctl start qfixpt.service \ No newline at end of file diff --git a/tools/stop b/tools/stop new file mode 100644 index 0000000..5d83ed9 --- /dev/null +++ b/tools/stop @@ -0,0 +1,4 @@ +#!/bin/bash + +systemctl daemon-reload +systemctl stop qfixpt.service \ No newline at end of file diff --git a/tools/test_branch.sh b/tools/test_branch.sh new file mode 100755 index 0000000..17cc2f1 --- /dev/null +++ b/tools/test_branch.sh @@ -0,0 +1,361 @@ +#!/bin/bash +# +# Unit tests for branch.sh script +# Run from project root: ./tools/test_branch.sh +# + +# Don't exit on error - we want to run all tests +set +e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Test counters +TESTS_RUN=0 +TESTS_PASSED=0 +TESTS_FAILED=0 + +# Save current branch +ORIGINAL_BRANCH=$(git branch --show-current) + +# Cleanup function +cleanup() { + # Remove test user cache files + rm -f ~/.qtx_branch_last_user 2>/dev/null || true + + # Return to original branch + git checkout "$ORIGINAL_BRANCH" 2>/dev/null || git checkout develop 2>/dev/null || true + + # Delete test branches + git branch 2>/dev/null | grep -E "(testuser|cached_user|user123|typetest|nametest|secureuser|validationtest)" | xargs -r git branch -D 2>/dev/null || true +} + +# Setup +trap cleanup EXIT + +echo "========================================" +echo " Branch Script Unit Tests" +echo "========================================" +echo "" + +# Test helper function - pass test +pass_test() { + local test_name="$1" + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + echo -e "${GREEN}βœ“${NC} PASS: $test_name" +} + +# Test helper function - fail test +fail_test() { + local test_name="$1" + local reason="$2" + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + echo -e "${RED}βœ—${NC} FAIL: $test_name${reason:+ - $reason}" +} + +echo "Running tests..." +echo "" + +# ==================== +# Test 1: Valid branch creation +# ==================== +echo "Test Suite 1: Valid Branch Creation" +echo "------------------------------------" + +cat << 'EOF' | timeout 10 ./tools/branch.sh &>/dev/null +1 +testuser +123 +valid branch +EOF + +if git rev-parse --verify "feature/testuser/SKL-123/valid_branch" &>/dev/null; then + pass_test "Create valid feature branch" +else + fail_test "Create valid feature branch" "Branch not found" +fi + +if [[ "$(git branch --show-current)" == "feature/testuser/SKL-123/valid_branch" ]]; then + pass_test "Checkout to created branch" +else + fail_test "Checkout to created branch" "Not on expected branch" +fi + +git checkout develop &>/dev/null +echo "" + +# ==================== +# Test 2: User cache functionality +# ==================== +echo "Test Suite 2: User Cache Functionality" +echo "---------------------------------------" + +# Create cache file +rm -f ~/.qtx_branch_last_user +cat << 'EOF' | timeout 10 ./tools/branch.sh &>/dev/null +6 +cached_user +456 +using cache +EOF + +if [[ -f ~/.qtx_branch_last_user ]]; then + pass_test "User cache file created" +else + fail_test "User cache file created" "File not found" +fi + +# Check permissions +PERMS=$(stat -f "%Lp" ~/.qtx_branch_last_user 2>/dev/null || stat -c "%a" ~/.qtx_branch_last_user 2>/dev/null) +if [[ "$PERMS" == "600" ]]; then + pass_test "User cache has secure permissions (600)" +else + fail_test "User cache has secure permissions (600)" "Got $PERMS" +fi + +if grep -q "cached_user" ~/.qtx_branch_last_user; then + pass_test "User cache contains correct value" +else + fail_test "User cache contains correct value" +fi + +git checkout develop &>/dev/null +echo "" + +# ==================== +# Test 3: Input validation +# ==================== +echo "Test Suite 3: Input Validation" +echo "-------------------------------" + +# Test invalid username with special characters +OUTPUT=$(cat << 'EOF' 2>/dev/null | timeout 5 ./tools/branch.sh 2>&1 || true +1 +user@invalid +EOF +) + +if echo "$OUTPUT" | grep -q "Error: user must be alphanumeric"; then + pass_test "Reject username with special characters" +else + fail_test "Reject username with special characters" +fi + +# Test non-numeric issue number +OUTPUT=$(cat << 'EOF' 2>/dev/null | timeout 5 ./tools/branch.sh 2>&1 || true +1 +validationtest +abc123 +EOF +) + +if echo "$OUTPUT" | grep -q "Error: issue number must be numeric"; then + pass_test "Reject non-numeric issue number" +else + fail_test "Reject non-numeric issue number" +fi + +# Test empty summary +OUTPUT=$(cat << 'EOF' 2>/dev/null | timeout 5 ./tools/branch.sh 2>&1 || true +1 +validationtest +999 + +EOF +) + +if echo "$OUTPUT" | grep -q "Error: summary is required"; then + pass_test "Reject empty summary" +else + fail_test "Reject empty summary" +fi + +# Test summary too long (over 100 chars) +LONG_SUMMARY="this is a very long summary that exceeds one hundred characters and should be rejected by the validation" +OUTPUT=$(cat << EOF 2>/dev/null | timeout 5 ./tools/branch.sh 2>&1 || true +1 +validationtest +999 +$LONG_SUMMARY +EOF +) + +if echo "$OUTPUT" | grep -q "Error: summary too long"; then + pass_test "Reject summary over 100 characters" +else + fail_test "Reject summary over 100 characters" +fi + +echo "" + +# ==================== +# Test 4: ISSUE_PREFIX extraction +# ==================== +echo "Test Suite 4: ISSUE_PREFIX Extraction" +echo "--------------------------------------" + +# Backup original Makefile.common +cp Makefile.common Makefile.common.backup + +# Test with different prefix +cat > Makefile.common << 'EOF' +ISSUE_PREFIX := TEST +GOPATH ?= $(shell go env GOPATH) +EOF + +cat << 'EOF' | timeout 10 ./tools/branch.sh &>/dev/null +1 +user123 +789 +different prefix +EOF + +# Restore original +mv Makefile.common.backup Makefile.common + +if git rev-parse --verify "feature/user123/TEST-789/different_prefix" &>/dev/null; then + pass_test "Create branch with custom ISSUE_PREFIX" +else + fail_test "Create branch with custom ISSUE_PREFIX" "Branch not found with TEST prefix" +fi + +git checkout develop &>/dev/null +echo "" + +# ==================== +# Test 5: Branch name formatting +# ==================== +echo "Test Suite 5: Branch Name Formatting" +echo "-------------------------------------" + +# Spaces should become underscores +BRANCH_OUTPUT=$(cat << 'EOF' | timeout 10 ./tools/branch.sh 2>&1 +2 +nametest +111 +spaces in summary here +EOF +) + +if echo "$BRANCH_OUTPUT" | grep -q "hotfix/nametest/SKL-111/spaces_in_summary_here"; then + pass_test "Spaces converted to underscores in branch name" +else + fail_test "Spaces converted to underscores in branch name" +fi + +git branch -D "hotfix/nametest/SKL-111/spaces_in_summary_here" &>/dev/null || true +git checkout develop &>/dev/null +echo "" + +# ==================== +# Test 6: Different branch types +# ==================== +echo "Test Suite 6: All Branch Types" +echo "-------------------------------" + +# Test feature type +cat << 'EOF' | timeout 10 ./tools/branch.sh &>/dev/null +1 +typetest +301 +test feature +EOF + +if git rev-parse --verify "feature/typetest/SKL-301/test_feature" &>/dev/null; then + pass_test "Branch type 'feature' works" + git branch -D "feature/typetest/SKL-301/test_feature" &>/dev/null +else + fail_test "Branch type 'feature' works" +fi + +# Test hotfix type +cat << 'EOF' | timeout 10 ./tools/branch.sh &>/dev/null +2 +typetest +302 +test hotfix +EOF + +if git rev-parse --verify "hotfix/typetest/SKL-302/test_hotfix" &>/dev/null; then + pass_test "Branch type 'hotfix' works" + git branch -D "hotfix/typetest/SKL-302/test_hotfix" &>/dev/null +else + fail_test "Branch type 'hotfix' works" +fi + +# Test fix type +cat << 'EOF' | timeout 10 ./tools/branch.sh &>/dev/null +6 +typetest +306 +test fix +EOF + +if git rev-parse --verify "fix/typetest/SKL-306/test_fix" &>/dev/null; then + pass_test "Branch type 'fix' works" + git branch -D "fix/typetest/SKL-306/test_fix" &>/dev/null +else + fail_test "Branch type 'fix' works" +fi + +git checkout develop &>/dev/null +echo "" + +# ==================== +# Test 7: Symlink attack prevention +# ==================== +echo "Test Suite 7: Security - Symlink Prevention" +echo "--------------------------------------------" + +# Create a symlink as the user file +USER_CACHE=~/.qtx_branch_last_user +TEMP_TARGET=$(mktemp) +rm -f "$USER_CACHE" +ln -s "$TEMP_TARGET" "$USER_CACHE" + +cat << 'EOF' | timeout 10 ./tools/branch.sh &>/dev/null +1 +secureuser +999 +security test +EOF + +# Check if symlink was removed and replaced with regular file +if [[ -L "$USER_CACHE" ]]; then + fail_test "Symlink removed before writing" "Symlink still exists (security risk)" +else + pass_test "Symlink removed before writing (secure)" +fi + +rm -f "$TEMP_TARGET" +git branch -D "feature/secureuser/SKL-999/security_test" &>/dev/null || true +git checkout develop &>/dev/null +echo "" + +# ==================== +# Summary +# ==================== +echo "========================================" +echo " Test Summary" +echo "========================================" +echo -e "Total: $TESTS_RUN" +echo -e "${GREEN}Passed: $TESTS_PASSED${NC}" +if [[ $TESTS_FAILED -gt 0 ]]; then + echo -e "${RED}Failed: $TESTS_FAILED${NC}" +else + echo -e "Failed: $TESTS_FAILED" +fi +echo "" + +if [[ $TESTS_FAILED -eq 0 ]]; then + echo -e "${GREEN}βœ“ All tests passed!${NC}" + exit 0 +else + echo -e "${RED}βœ— Some tests failed!${NC}" + exit 1 +fi diff --git a/tools/unzip-qfixpt.sh b/tools/unzip-qfixpt.sh new file mode 100644 index 0000000..6a138b5 --- /dev/null +++ b/tools/unzip-qfixpt.sh @@ -0,0 +1,8 @@ +# This script should be installed in the server where the service will run +# To copy it into the server you can use the push_deploy_qfixpt.sh script +# We need this scripts because the deploy user (quantex) doesn't have enough permissions to +# run the service directly. + +cd /home/quantex/qfixpt +gzip -fkd qfixpt.gz +chmod +x qfixpt diff --git a/update_library.sh b/update_library.sh new file mode 100644 index 0000000..d01ddb3 --- /dev/null +++ b/update_library.sh @@ -0,0 +1,71 @@ +#!/bin/bash +SUCCESS="\033[32m" +ERROR="\033[31m" +NC="\033[0m" + +project_repository="fix" +project_directory=$(pwd) +fixFile=$1 + +cd .. +mkdir tmp +cd tmp +git clone https://github.com/quickfixgo/quickfix + +# Get the current working directory +tmp_directory=$(pwd) +# Set the folder path dynamically +cloned_repo="$tmp_directory/quickfix" + + +if [ -d "$cloned_repo" ]; then + cd quickfix + + last_update=$(git log -1 --format=%cd) + existing_update=$(cat "$project_directory/last_update_info.txt") + + if [ "$existing_update" = "$last_update" ]; then + echo -e "${SUCCESS}The library is already updated, there are no new changes in the repository${NC}" + + # delete tmp folder + rm -rf "$tmp_directory" + + exit 0 + fi + + # replace the fix file in the project + rm spec/$fixFile + cp $project_directory/spec/$fixFile spec/$fixFile + + # generate the library code + make + make generate + + # rename go.mod and go.sum + mv go.mod go.mod_ + mv go.sum go.sum_ + + # update the last update date + echo "$last_update" > $project_directory/last_update_info.txt + echo -e "${SUCCESS}Last update information has been updated in last_update_info.txt${NC}" + + echo "Clearing existing contents of $project_directory/quickfix..." + rm -rf "$project_directory/quickfix"/* + + echo "Copying new contents to $project_directory/quickfix..." + cp -R "$cloned_repo/"* "$project_directory/quickfix" + + # delete tmp folder + rm -rf "$tmp_directory" + + #replace library imports + cd $project_directory + find quickfix -type f -exec sed -i 's|github.com/quickfixgo/quickfix|quantex.com/qfixpt/quickfix|g' {} + + + echo "Library Updated" +else + echo -e "${ERROR}Could not clone the repo${NC}" + # delete tmp folder + rm -rf "$tmp_directory" + exit 1 +fi \ No newline at end of file