first commit

This commit is contained in:
Ramiro Paz
2026-03-11 10:54:11 -03:00
parent bfeecb334a
commit aa0525a78c
85 changed files with 14079 additions and 0 deletions

115
.gitignore vendored Normal file
View File

@ -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/

View File

@ -0,0 +1,3 @@
---
MULTIDB_ACCESS_TOKEN: <ACCESS_TOKEN>
USERNAME: <USERNAME>

99
.gitlab-ci.yml Normal file
View File

@ -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

View File

@ -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

View File

@ -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

24
AGENTS.md Normal file
View File

@ -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=<alias>` pushes a build using the target defined in `build/deploy.sh`.
## Coding Style & Naming Conventions
Run `make fmt` to apply gofumpt, gci, and goimports; Gos 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/<module>`. 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/<package>` and store fixtures under package-level `testdata/` directories.
## Commit & Pull Request Guidelines
Use `make branch t=<type> u=<user> n=<issue> 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=<alias>`. When exploring logging formats pass `-f text|json|tint1|tint2`, and follow the `README.md` guidance for multi-DB runs that require elevated permissions.

496
CLAUDE.md Normal file
View File

@ -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

138
Makefile Normal file
View File

@ -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

62
Makefile.common Normal file
View File

@ -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

31
checklist.md Normal file
View File

@ -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.

View File

@ -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`

110
go.mod Normal file
View File

@ -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
)

736
go.sum Normal file
View File

@ -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=

12
linter_version.txt Normal file
View File

@ -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

201
main.go Normal file
View File

@ -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)
}
}

1
res/file.txt Normal file
View File

@ -0,0 +1 @@
To Embed

4751
spec/FIX50SP2-DS-CORI.xml Normal file

File diff suppressed because it is too large Load Diff

5
src/app/mode/mode.go Normal file
View File

@ -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

122
src/app/model.go Normal file
View File

@ -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

118
src/app/version/version.go Normal file
View File

@ -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()
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}
}
}

View File

@ -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
}

View File

@ -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))
}

View File

@ -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())
}()
}

View File

@ -0,0 +1 @@
package rest

View File

@ -0,0 +1 @@
package rest

View File

@ -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
}

16
src/client/data/data.go Normal file
View File

@ -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{}
}

View File

@ -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)
}

142
src/client/notify/common.go Normal file
View File

@ -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
}

View File

@ -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': '<b>Environment:</b> %s'
}
},
{
'textParagraph': {
'text': '<b>Message:</b> %s'
}
},
{
'textParagraph': {
'text': '<b>Build:</b> %s'
}
},
{
'textParagraph': {
'text': '<b>Time:</b> %s'
}
},
{
'textParagraph': {
'text': '<b>Hostname:</b> %s'
}
},
{
'textParagraph': {
'text': '<b>IP:</b> %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
}

View File

@ -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
}

118
src/client/res/resources.go Normal file
View File

@ -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)
}

42
src/client/store/external/auth.go vendored Normal file
View File

@ -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
}

273
src/client/store/external/manager.go vendored Normal file
View File

@ -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
}

141
src/client/store/external/user.go vendored Normal file
View File

@ -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
}

View File

@ -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
}

39
src/cmd/base.go Normal file
View File

@ -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
}
}

83
src/cmd/example/async.go Normal file
View File

@ -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)
}

View File

@ -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
}

50
src/cmd/example/logs.go Normal file
View File

@ -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
}

View File

@ -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))
}

View File

@ -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
}

347
src/common/jwttoken/jwt.go Normal file
View File

@ -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
}

View File

@ -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)
}

137
src/common/logger/logger.go Normal file
View File

@ -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()
}
}

View File

@ -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

View File

@ -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)
}

View File

@ -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()
}

25
src/domain/model.go Normal file
View File

@ -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
}

27
src/domain/notify.go Normal file
View File

@ -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
}

40
tools/backup.sh Normal file
View File

@ -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

156
tools/branch.sh Executable file
View File

@ -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

53
tools/build.sh Executable file
View File

@ -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

25
tools/check/check-gogenerate.sh Executable file
View File

@ -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

26
tools/check/check-tidy.sh Executable file
View File

@ -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

View File

@ -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

View File

@ -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

230
tools/check/go.mod Normal file
View File

@ -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
)

985
tools/check/go.sum Normal file
View File

@ -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=

View File

@ -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."

83
tools/check/golangci.yml Normal file
View File

@ -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

75
tools/check/revive.toml Normal file
View File

@ -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]

18
tools/deploy-with-ssh-key.sh Executable file
View File

@ -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"

33
tools/deploy.sh Executable file
View File

@ -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"

View File

@ -0,0 +1,3 @@
#!/bin/bash
qscp linter_version.txt quantex@app.quantex.com.ar:~/linter_version.txt

43
tools/first_deploy.sh Executable file
View File

@ -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"

110
tools/generate-jwt.sh Executable file
View File

@ -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 <token> <issuer> <expiry> <secret>\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"

View File

@ -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"

21
tools/print-version.sh Executable file
View File

@ -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

65
tools/push-script.sh Executable file
View File

@ -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"

View File

@ -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/

View File

@ -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

19
tools/qfixpt.service Normal file
View File

@ -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

76
tools/remove_multidb.sh Executable file
View File

@ -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."

4
tools/restart Normal file
View File

@ -0,0 +1,4 @@
#!/bin/bash
systemctl daemon-reload
systemctl restart skeleton.service

20
tools/rollback.sh Normal file
View File

@ -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

4
tools/start Normal file
View File

@ -0,0 +1,4 @@
#!/bin/bash
systemctl daemon-reload
systemctl start qfixpt.service

4
tools/stop Normal file
View File

@ -0,0 +1,4 @@
#!/bin/bash
systemctl daemon-reload
systemctl stop qfixpt.service

361
tools/test_branch.sh Executable file
View File

@ -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

8
tools/unzip-qfixpt.sh Normal file
View File

@ -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

71
update_library.sh Normal file
View File

@ -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