Compare commits
2 Commits
bfeecb334a
...
b45efdb297
| Author | SHA1 | Date | |
|---|---|---|---|
| b45efdb297 | |||
| aa0525a78c |
115
.gitignore
vendored
Normal file
115
.gitignore
vendored
Normal 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/
|
||||
3
.gitlab-ci-local-variables.yml
Normal file
3
.gitlab-ci-local-variables.yml
Normal file
@ -0,0 +1,3 @@
|
||||
---
|
||||
MULTIDB_ACCESS_TOKEN: <ACCESS_TOKEN>
|
||||
USERNAME: <USERNAME>
|
||||
99
.gitlab-ci.yml
Normal file
99
.gitlab-ci.yml
Normal 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
|
||||
26
.gitlab/issue_templates/Default.md
Normal file
26
.gitlab/issue_templates/Default.md
Normal 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
|
||||
|
||||
30
.gitlab/merge_request_templates/Default.md
Normal file
30
.gitlab/merge_request_templates/Default.md
Normal 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
24
AGENTS.md
Normal 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; Go’s formatter enforces tab indentation and canonical spacing. Follow idiomatic naming—PascalCase for exported symbols, camelCase for internals, lowercase filenames—and keep package paths under `quantex.com/skeleton/<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
496
CLAUDE.md
Normal 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
138
Makefile
Normal 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
62
Makefile.common
Normal 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
31
checklist.md
Normal 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.
|
||||
417
docs/JWT_SERVICE_AUTHENTICATION.md
Normal file
417
docs/JWT_SERVICE_AUTHENTICATION.md
Normal 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
110
go.mod
Normal 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
736
go.sum
Normal 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
12
linter_version.txt
Normal 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
201
main.go
Normal 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
1
res/file.txt
Normal file
@ -0,0 +1 @@
|
||||
To Embed
|
||||
6710
spec/FIX50SP2.xml
Normal file
6710
spec/FIX50SP2.xml
Normal file
File diff suppressed because it is too large
Load Diff
5
src/app/mode/mode.go
Normal file
5
src/app/mode/mode.go
Normal 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
122
src/app/model.go
Normal 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
118
src/app/version/version.go
Normal 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()
|
||||
}
|
||||
21
src/client/api/async/model.go
Normal file
21
src/client/api/async/model.go
Normal 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
|
||||
186
src/client/api/async/mqtt_client.go
Normal file
186
src/client/api/async/mqtt_client.go
Normal 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
|
||||
}
|
||||
290
src/client/api/rest/controller.go
Normal file
290
src/client/api/rest/controller.go
Normal 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
|
||||
}
|
||||
254
src/client/api/rest/midlewares.go
Normal file
254
src/client/api/rest/midlewares.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/client/api/rest/model.go
Normal file
18
src/client/api/rest/model.go
Normal 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
|
||||
}
|
||||
39
src/client/api/rest/routes.go
Normal file
39
src/client/api/rest/routes.go
Normal 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))
|
||||
}
|
||||
95
src/client/api/rest/server.go
Normal file
95
src/client/api/rest/server.go
Normal 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())
|
||||
}()
|
||||
}
|
||||
1
src/client/api/rest/traslator.go
Normal file
1
src/client/api/rest/traslator.go
Normal file
@ -0,0 +1 @@
|
||||
package rest
|
||||
1
src/client/api/rest/validator.go
Normal file
1
src/client/api/rest/validator.go
Normal file
@ -0,0 +1 @@
|
||||
package rest
|
||||
31
src/client/config/config.go
Normal file
31
src/client/config/config.go
Normal 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
16
src/client/data/data.go
Normal 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{}
|
||||
}
|
||||
32
src/client/notify/all/all.go
Normal file
32
src/client/notify/all/all.go
Normal 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
142
src/client/notify/common.go
Normal 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
|
||||
}
|
||||
271
src/client/notify/google/google_chat.go
Normal file
271
src/client/notify/google/google_chat.go
Normal 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
|
||||
}
|
||||
122
src/client/notify/slack/slack.go
Normal file
122
src/client/notify/slack/slack.go
Normal 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
118
src/client/res/resources.go
Normal 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
42
src/client/store/external/auth.go
vendored
Normal 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
273
src/client/store/external/manager.go
vendored
Normal 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
141
src/client/store/external/user.go
vendored
Normal 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
|
||||
}
|
||||
72
src/client/store/manager.go
Normal file
72
src/client/store/manager.go
Normal 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
39
src/cmd/base.go
Normal 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
83
src/cmd/example/async.go
Normal 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)
|
||||
}
|
||||
78
src/cmd/example/external.go
Normal file
78
src/cmd/example/external.go
Normal 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
50
src/cmd/example/logs.go
Normal 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
|
||||
}
|
||||
43
src/cmd/example/tracerr.go
Normal file
43
src/cmd/example/tracerr.go
Normal 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))
|
||||
}
|
||||
55
src/cmd/service/service.go
Normal file
55
src/cmd/service/service.go
Normal 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
347
src/common/jwttoken/jwt.go
Normal 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
|
||||
}
|
||||
85
src/common/logger/handler.go
Normal file
85
src/common/logger/handler.go
Normal 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
137
src/common/logger/logger.go
Normal 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()
|
||||
}
|
||||
}
|
||||
56
src/common/logger/tint/buffer.go
Normal file
56
src/common/logger/tint/buffer.go
Normal 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
|
||||
522
src/common/logger/tint/handler.go
Normal file
522
src/common/logger/tint/handler.go
Normal 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)
|
||||
}
|
||||
144
src/common/tracerr/tracerr.go
Normal file
144
src/common/tracerr/tracerr.go
Normal 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
25
src/domain/model.go
Normal 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
27
src/domain/notify.go
Normal 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
40
tools/backup.sh
Normal 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
156
tools/branch.sh
Executable 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
53
tools/build.sh
Executable 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
25
tools/check/check-gogenerate.sh
Executable 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
26
tools/check/check-tidy.sh
Executable 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
|
||||
24
tools/check/check_parser_replace.sh
Executable file
24
tools/check/check_parser_replace.sh
Executable 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
|
||||
7
tools/check/errcheck_excludes.txt
Normal file
7
tools/check/errcheck_excludes.txt
Normal 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
230
tools/check/go.mod
Normal 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
985
tools/check/go.sum
Normal 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=
|
||||
116
tools/check/golangci-from-gitea.yml
Normal file
116
tools/check/golangci-from-gitea.yml
Normal 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
83
tools/check/golangci.yml
Normal 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
75
tools/check/revive.toml
Normal 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
18
tools/deploy-with-ssh-key.sh
Executable 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
33
tools/deploy.sh
Executable 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"
|
||||
|
||||
|
||||
3
tools/deploy_new_linters.sh
Normal file
3
tools/deploy_new_linters.sh
Normal 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
43
tools/first_deploy.sh
Executable 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
110
tools/generate-jwt.sh
Executable 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"
|
||||
28
tools/global_conf_example.toml
Normal file
28
tools/global_conf_example.toml
Normal 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
21
tools/print-version.sh
Executable 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
65
tools/push-script.sh
Executable 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"
|
||||
|
||||
8
tools/qfixpt-watcher.path
Normal file
8
tools/qfixpt-watcher.path
Normal 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/
|
||||
10
tools/qfixpt-watcher.service
Normal file
10
tools/qfixpt-watcher.service
Normal 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
19
tools/qfixpt.service
Normal 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
76
tools/remove_multidb.sh
Executable 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
4
tools/restart
Normal file
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl restart skeleton.service
|
||||
20
tools/rollback.sh
Normal file
20
tools/rollback.sh
Normal 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
4
tools/start
Normal file
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl start qfixpt.service
|
||||
4
tools/stop
Normal file
4
tools/stop
Normal file
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl stop qfixpt.service
|
||||
361
tools/test_branch.sh
Executable file
361
tools/test_branch.sh
Executable 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
8
tools/unzip-qfixpt.sh
Normal 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
71
update_library.sh
Normal 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
|
||||
Reference in New Issue
Block a user