From db2bcee8f731ec91ee1382a13db6cef4a3ff985e Mon Sep 17 00:00:00 2001 From: Logan LaNou Date: Tue, 26 May 2026 21:28:35 -0500 Subject: [PATCH 1/5] chore: pin templ/sqlc/goose via go tool and fix CI Tool dependencies (templ, sqlc, goose) are now pinned in go.mod via the `tool` directive and invoked as `go tool ` in .air.toml, the Makefile, and CI. This keeps the generator version locked to the runtime version and fixes the build failure caused by drift (a globally-installed templ generator emitting `templ.ResolveAttributeValue`, undefined in the older go.mod runtime). - go.mod: add tool directive (templ v0.3.1020, sqlc v1.31.1, goose v3.27.1); bump module to go 1.26. - .air.toml / Makefile: invoke generators via `go tool`; slim `setup`/`setup-ci` (only air + golangci-lint need global installs); add `make templ-update`. - CI: derive Go version from go.mod (was pinned to 1.23, which couldn't build the 1.26 module), generate via `go tool`, drop the global tool installs, and add dev-tag build/test steps. - Clear pre-existing lint blockers so CI's golangci-lint passes: check deferred Close/Quit errors (smtp.go, admin_api.go) and remove dead functions (cmd/build/main.go). Co-Authored-By: Claude Opus 4.7 (1M context) --- .air.toml | 6 +- .github/workflows/ci.yml | 21 +- Makefile | 46 +++- cmd/build/main.go | 41 --- go.mod | 114 ++++++++- go.sum | 465 ++++++++++++++++++++++++++++++---- internal/handler/admin_api.go | 4 +- internal/notify/smtp.go | 8 +- 8 files changed, 568 insertions(+), 137 deletions(-) diff --git a/.air.toml b/.air.toml index 5916336..7ff721e 100644 --- a/.air.toml +++ b/.air.toml @@ -5,7 +5,7 @@ tmp_dir = "tmp" [build] args_bin = [] bin = "./tmp/rowetech" - cmd = "go build -o ./tmp/rowetech ./cmd/server" + cmd = "go build -tags dev -o ./tmp/rowetech ./cmd/server" delay = 1000 exclude_dir = ["assets", "tmp", "vendor", "testdata", "node_modules", "static", "internal/database/sqlc", ".next", "out"] exclude_file = ["go.sum"] @@ -23,8 +23,8 @@ tmp_dir = "tmp" post_cmd = [] pre_cmd = [ "lsof -ti:${PORT:-3000} | xargs kill -9 2>/dev/null || true; sleep 0.5", - "templ generate", - "sqlc generate -f sqlc/sqlc.yaml 2>/dev/null || true", + "go tool templ generate", + "go tool sqlc generate -f sqlc/sqlc.yaml 2>/dev/null || true", "go mod tidy" ] rerun = false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f65048..c00c859 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,21 +14,20 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: - go-version: '1.23' + # Keep CI's Go in lockstep with the module (go.mod is on 1.26). + go-version-file: go.mod cache: false - uses: actions/setup-node@v6 with: node-version: '20' - - name: Install Go tools - run: | - go install github.com/a-h/templ/cmd/templ@latest - go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest - name: Install npm dependencies run: npm ci - name: Generate code + # templ/sqlc are pinned via the `tool` directive in go.mod, so the + # generator version always matches the runtime — no global installs. run: | - templ generate - sqlc generate -f sqlc/sqlc.yaml + go tool templ generate + go tool sqlc generate -f sqlc/sqlc.yaml - name: Build CSS run: make css - name: golangci-lint @@ -37,6 +36,10 @@ jobs: version: v2.1.6 args: --timeout=5m - name: Run tests - run: go test -v -race ./... - - name: Build + run: go test -race ./... + - name: Run tests (dev tag) + run: go test -race -tags dev ./... + - name: Build (production) run: go build -o bin/rowetech ./cmd/server + - name: Build (dev tag) + run: go build -tags dev -o bin/rowetech-dev ./cmd/server diff --git a/Makefile b/Makefile index dd109e1..7dd9bf3 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,14 @@ SHELL := /bin/bash -.PHONY: dev build build-static test lint generate css css-watch migrate migrate-down migrate-status migrate-create setup setup-ci clean run help +.PHONY: dev build build-static test test-dev lint generate css css-watch migrate migrate-down migrate-status migrate-create setup setup-ci clean run help templ-update BINARY_NAME=rowetech MIGRATIONS_DIR=migrations DIST_DIR=dist +# Hot-reload dev server. Air builds with `-tags dev` (see .air.toml), which +# compiles in the loopback-only auth bypass at /auth/dev/login so admin pages +# can be tested without Clerk. See CLAUDE.md "Dev-Only Auth Bypass". dev: @if [ -f tmp/air-combined.log ]; then \ mv tmp/air-combined.log tmp/air-combined-$$(date +%Y%m%d-%H%M%S).log; \ @@ -13,6 +16,8 @@ dev: @ls -t tmp/air-combined-*.log 2>/dev/null | tail -n +6 | xargs rm -f 2>/dev/null || true @air 2>&1 | tee tmp/air-combined.log +# Production build. Intentionally OMITS `-tags dev`, so the dev auth bypass is +# compiled out entirely (the !dev stub is used instead). build: generate css go build -o $(BINARY_NAME) ./cmd/server @@ -26,13 +31,17 @@ build-static: generate css test: go test -v -race ./... +# Run the dev-tagged tests too (auth bypass loopback gating lives behind -tags dev). +test-dev: + go test -race -tags dev ./... + lint: golangci-lint run - templ fmt templates/ + go tool templ fmt templates/ generate: - templ generate - @if command -v sqlc &> /dev/null; then sqlc generate -f sqlc/sqlc.yaml; fi + go tool templ generate + go tool sqlc generate -f sqlc/sqlc.yaml css: npx @tailwindcss/cli -i static/css/input.css -o static/css/output.css --minify @@ -41,33 +50,40 @@ css-watch: npx @tailwindcss/cli -i static/css/input.css -o static/css/output.css --watch migrate: - goose -dir $(MIGRATIONS_DIR) sqlite3 "$$DATABASE_URL" up + go tool goose -dir $(MIGRATIONS_DIR) sqlite3 "$$DATABASE_URL" up migrate-down: - goose -dir $(MIGRATIONS_DIR) sqlite3 "$$DATABASE_URL" down + go tool goose -dir $(MIGRATIONS_DIR) sqlite3 "$$DATABASE_URL" down migrate-status: - goose -dir $(MIGRATIONS_DIR) sqlite3 "$$DATABASE_URL" status + go tool goose -dir $(MIGRATIONS_DIR) sqlite3 "$$DATABASE_URL" status migrate-create: ifndef NAME $(error NAME is required. Usage: make migrate-create NAME=create_users) endif - goose -dir $(MIGRATIONS_DIR) create $(NAME) sql + go tool goose -dir $(MIGRATIONS_DIR) create $(NAME) sql +# Tool dependencies (templ, sqlc, goose) are pinned via the `tool` directive in +# go.mod and invoked through `go tool ` everywhere, so the generator +# version always matches the runtime version. Only air and golangci-lint still +# need a global install since they are not in go.mod. setup: go install github.com/air-verse/air@latest - go install github.com/a-h/templ/cmd/templ@latest - go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest - go install github.com/pressly/goose/v3/cmd/goose@latest go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest npm install -# CI/CD setup (minimal - for Vercel) +# CI/CD setup (minimal - for Vercel). templ/sqlc come from `go tool`. setup-ci: - go install github.com/a-h/templ/cmd/templ@latest npm install +# Bump the templ runtime and its tool pin together so the generator version +# always matches the runtime version. Avoids the drift that breaks builds. +templ-update: + go get -u github.com/a-h/templ@latest + go get -tool github.com/a-h/templ/cmd/templ@latest + go mod tidy + clean: rm -f $(BINARY_NAME) rm -rf tmp/ @@ -83,6 +99,7 @@ help: @echo " build - Build the binary" @echo " build-static - Build static site for Vercel" @echo " test - Run tests" + @echo " test-dev - Run tests including -tags dev (auth bypass)" @echo " lint - Run golangci-lint and templ fmt" @echo " generate - Generate templ and sqlc code" @echo " css - Build Tailwind CSS" @@ -91,7 +108,8 @@ help: @echo " migrate-down - Rollback last migration" @echo " migrate-status - Show migration status" @echo " migrate-create - Create new migration (NAME=xxx)" - @echo " setup - Install development tools" + @echo " setup - Install development tools (air, golangci-lint)" @echo " setup-ci - Install CI tools (Vercel)" + @echo " templ-update - Bump templ runtime + tool pin together" @echo " clean - Remove build artifacts" @echo " run - Build and run the server" diff --git a/cmd/build/main.go b/cmd/build/main.go index f8a2881..a2832ac 100644 --- a/cmd/build/main.go +++ b/cmd/build/main.go @@ -231,44 +231,3 @@ func getStaticGalleryCategories() []string { "EDM", } } - -// getStaticPageImages returns hardcoded page images grouped by page name -func getStaticPageImages() map[string][]models.PageImage { - return map[string][]models.PageImage{ - "home": { - {ID: 1, PageName: "home", ImageKey: "hero", ImageUrl: "/static/images/customer/laser-marking-foba.jpg", Label: "Hero Background", AltText: "Laser marking equipment at RoweTech", SortOrder: 1}, - {ID: 2, PageName: "home", ImageKey: "service-mold-repair", ImageUrl: "/static/images/customer/mold-repair-bench.jpg", Label: "Mold Repair Service", AltText: "Mold repair and maintenance workstation", SortOrder: 2}, - {ID: 3, PageName: "home", ImageKey: "service-fixtures", ImageUrl: "/static/images/customer/mold-repair-microscope.jpg", Label: "Custom Fixtures Service", AltText: "Microscope inspection workstation", SortOrder: 3}, - {ID: 4, PageName: "home", ImageKey: "service-eoat", ImageUrl: "/static/images/customer/laser-marking-foba.jpg", Label: "EOAT Service", AltText: "Advanced equipment at RoweTech", SortOrder: 4}, - {ID: 5, PageName: "home", ImageKey: "service-cnc", ImageUrl: "/static/images/customer/machined-cavity-detail.jpg", Label: "CNC Machining Service", AltText: "Precision-machined mold cavity", SortOrder: 5}, - {ID: 6, PageName: "home", ImageKey: "why-us-1", ImageUrl: "/static/images/customer/cmm-inspection-room.jpg", Label: "Why Choose Us Image 1", AltText: "CMM inspection equipment", SortOrder: 6}, - {ID: 7, PageName: "home", ImageKey: "why-us-2", ImageUrl: "/static/images/customer/mold-repair-workstation.jpg", Label: "Why Choose Us Image 2", AltText: "Mold repair workstation", SortOrder: 7}, - {ID: 8, PageName: "home", ImageKey: "cta", ImageUrl: "/static/images/customer/cmm-inspection-room.jpg", Label: "CTA Background", AltText: "Coordinate measuring machine at RoweTech", SortOrder: 8}, - }, - "about": { - {ID: 9, PageName: "about", ImageKey: "hero", ImageUrl: "/static/images/customer/cmm-inspection-room.jpg", Label: "Hero Background", AltText: "Coordinate measuring machine inspection area", SortOrder: 1}, - {ID: 10, PageName: "about", ImageKey: "story", ImageUrl: "/static/images/customer/mold-repair-workstation.jpg", Label: "Our Story Image", AltText: "Mold repair workstation", SortOrder: 2}, - }, - "services": { - {ID: 11, PageName: "services", ImageKey: "hero", ImageUrl: "/static/images/customer/machined-cavity-detail.jpg", Label: "Hero Background", AltText: "Precision-machined mold cavity", SortOrder: 1}, - {ID: 12, PageName: "services", ImageKey: "mold-repair", ImageUrl: "/static/images/customer/mold-repair-bench.jpg", Label: "Mold Repair Section", AltText: "Plastic injection mold repair bench", SortOrder: 2}, - {ID: 13, PageName: "services", ImageKey: "fixtures", ImageUrl: "/static/images/customer/mold-repair-microscope.jpg", Label: "Fixtures Section", AltText: "Microscope inspection workstation", SortOrder: 3}, - {ID: 14, PageName: "services", ImageKey: "eoat", ImageUrl: "/static/images/customer/laser-marking-foba.jpg", Label: "EOAT Section", AltText: "Advanced equipment at RoweTech", SortOrder: 4}, - {ID: 15, PageName: "services", ImageKey: "cnc", ImageUrl: "/static/images/customer/machined-cavity-detail.jpg", Label: "CNC Section", AltText: "Precision-machined mold cavity", SortOrder: 5}, - }, - "capabilities": { - {ID: 16, PageName: "capabilities", ImageKey: "hero", ImageUrl: "/static/images/customer/cmm-inspection-room.jpg", Label: "Hero Background", AltText: "CMM inspection equipment", SortOrder: 1}, - }, - "gallery": { - {ID: 17, PageName: "gallery", ImageKey: "hero", ImageUrl: "/static/images/customer/laser-marking-foba.jpg", Label: "Hero Background", AltText: "Laser marking workstation", SortOrder: 1}, - }, - "contact": { - {ID: 18, PageName: "contact", ImageKey: "hero", ImageUrl: "/static/images/customer/mold-repair-workstation.jpg", Label: "Hero Background", AltText: "Mold repair workstation at RoweTech", SortOrder: 1}, - }, - } -} - -// getStaticPageNames returns all page names that have images -func getStaticPageNames() []string { - return []string{"home", "about", "services", "capabilities", "gallery", "contact"} -} diff --git a/go.mod b/go.mod index b0fb341..8c9d309 100644 --- a/go.mod +++ b/go.mod @@ -1,32 +1,120 @@ module rowetech -go 1.23.0 +go 1.26.0 require ( - github.com/a-h/templ v0.3.977 - github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/a-h/templ v0.3.1020 + github.com/golang-jwt/jwt/v5 v5.3.1 github.com/labstack/echo/v4 v4.13.3 github.com/lmittmann/tint v1.0.6 - modernc.org/sqlite v1.34.5 + github.com/pressly/goose/v3 v3.27.1 + modernc.org/sqlite v1.49.1 ) require ( + cel.dev/expr v0.25.1 // indirect + filippo.io/edwards25519 v1.2.0 // indirect + github.com/ClickHouse/ch-go v0.71.0 // indirect + github.com/ClickHouse/clickhouse-go/v2 v2.45.0 // indirect + github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect + github.com/andybalholm/brotli v1.2.1 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cli/browser v1.3.0 // indirect + github.com/coder/websocket v1.8.14 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/cubicdaiya/gonp v1.0.4 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/elastic/go-sysinfo v1.15.4 // indirect + github.com/elastic/go-windows v1.0.2 // indirect + github.com/fatih/color v1.16.0 // indirect + github.com/fatih/structtag v1.2.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-faster/city v1.0.1 // indirect + github.com/go-faster/errors v0.7.1 // indirect + github.com/go-sql-driver/mysql v1.9.3 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect + github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect + github.com/google/cel-go v0.28.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.9.2 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/jonboulle/clockwork v0.5.0 // indirect + github.com/klauspost/compress v1.18.5 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/mattn/go-isatty v0.0.21 // indirect + github.com/mfridman/interpolate v0.0.2 // indirect + github.com/mfridman/xflag v0.1.0 // indirect + github.com/microsoft/go-mssqldb v1.9.8 // indirect + github.com/natefinch/atomic v1.0.1 // indirect + github.com/ncruces/go-sqlite3 v0.32.0 // indirect + github.com/ncruces/go-strftime v1.0.0 // indirect + github.com/ncruces/julianday v1.0.0 // indirect + github.com/paulmach/orb v0.13.0 // indirect + github.com/pganalyze/pg_query_go/v6 v6.2.2 // indirect + github.com/pierrec/lz4/v4 v4.1.26 // indirect + github.com/pingcap/errors v0.11.5-0.20250523034308-74f78ae071ee // indirect + github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86 // indirect + github.com/pingcap/log v1.1.0 // indirect + github.com/pingcap/tidb/pkg/parser v0.0.0-20260418072757-ce92298d1124 // indirect + github.com/prometheus/procfs v0.20.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/riza-io/grpc-go v0.2.0 // indirect + github.com/segmentio/asm v1.2.1 // indirect + github.com/sethvargo/go-retry v0.3.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/spf13/cobra v1.10.2 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/sqlc-dev/doubleclick v1.0.0 // indirect + github.com/sqlc-dev/sqlc v1.31.1 // indirect + github.com/tetratelabs/wazero v1.11.0 // indirect + github.com/tursodatabase/libsql-client-go v0.0.0-20251219100830-236aa1ff8acc // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect - golang.org/x/crypto v0.40.0 // indirect - golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect - golang.org/x/net v0.42.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/text v0.27.0 // indirect + github.com/vertica/vertica-sql-go v1.3.6 // indirect + github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 // indirect + github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect + github.com/ydb-platform/ydb-go-genproto v0.0.0-20260311095541-ebbf792c1180 // indirect + github.com/ydb-platform/ydb-go-sdk/v3 v3.135.0 // indirect + github.com/ziutek/mymysql v1.5.4 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.1 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.50.0 // indirect + golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/net v0.53.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/text v0.36.0 // indirect golang.org/x/time v0.8.0 // indirect - modernc.org/libc v1.61.6 // indirect + golang.org/x/tools v0.44.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 // indirect + google.golang.org/grpc v1.80.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + howett.net/plist v1.0.1 // indirect + modernc.org/libc v1.72.1 // indirect modernc.org/mathutil v1.7.1 // indirect - modernc.org/memory v1.8.0 // indirect + modernc.org/memory v1.11.0 // indirect +) + +tool ( + github.com/a-h/templ/cmd/templ + github.com/pressly/goose/v3/cmd/goose + github.com/sqlc-dev/sqlc/cmd/sqlc ) diff --git a/go.sum b/go.sum index cc816c8..f617afc 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,165 @@ -github.com/a-h/templ v0.3.977 h1:kiKAPXTZE2Iaf8JbtM21r54A8bCNsncrfnokZZSrSDg= -github.com/a-h/templ v0.3.977/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +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= +filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= +filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 h1:E4MgwLBGeVB5f2MdcIVD3ELVAWpr+WD6MUe1i+tM/PA= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0/go.mod h1:Y2b/1clN4zsAoUd/pgNAQHjLDnTis/6ROkUfyob6psM= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/ClickHouse/ch-go v0.71.0 h1:bUdZ/EZj/LcVHsMqaRUP2holqygrPWQKeMjc6nZoyRM= +github.com/ClickHouse/ch-go v0.71.0/go.mod h1:NwbNc+7jaqfY58dmdDUbG4Jl22vThgx1cYjBw0vtgXw= +github.com/ClickHouse/clickhouse-go/v2 v2.45.0 h1:iHt15nA4iYhfde5bDQAcLAat9BAh7B5ksPRNRa4UI7s= +github.com/ClickHouse/clickhouse-go/v2 v2.45.0/go.mod h1:giJfUVlMkcfUEPVfRpt51zZaGEx9i17gCos8gBl392c= +github.com/a-h/parse v0.0.0-20250122154542-74294addb73e h1:HjVbSQHy+dnlS6C3XajZ69NYAb5jbGNfHanvm1+iYlo= +github.com/a-h/parse v0.0.0-20250122154542-74294addb73e/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ= +github.com/a-h/templ v0.3.1020 h1:ypAT/L5ySWEnZ6Zft/5yfoWXYYkhFNvEFOeeqecg4tw= +github.com/a-h/templ v0.3.1020/go.mod h1:A2DlK61v+K+NRoGnhmYbNYVmtYHcFO5/AisMvBdDxTM= +github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro= +github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/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/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= +github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= +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/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/cubicdaiya/gonp v1.0.4 h1:ky2uIAJh81WiLcGKBVD5R7KsM/36W6IqqTy6Bo6rGws= +github.com/cubicdaiya/gonp v1.0.4/go.mod h1:iWGuP/7+JVTn02OWhRemVbMmG1DOUnmrGTYYACpOI0I= +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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -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/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/elastic/go-sysinfo v1.8.1/go.mod h1:JfllUnzoQV/JRYymbH3dO1yggI3mV2oTKSXsDHM+uIM= +github.com/elastic/go-sysinfo v1.15.4 h1:A3zQcunCxik14MgXu39cXFXcIw2sFXZ0zL886eyiv1Q= +github.com/elastic/go-sysinfo v1.15.4/go.mod h1:ZBVXmqS368dOn/jvijV/zHLfakWTYHBZPk3G244lHrU= +github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU= +github.com/elastic/go-windows v1.0.2 h1:yoLLsAsV5cfg9FLhZ9EXZ2n2sQFKeDYrHenkcivY4vI= +github.com/elastic/go-windows v1.0.2/go.mod h1:bGcDpBzXgYSqM0Gx3DM4+UxFj300SZLixie9u9ixLM8= +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.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +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/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= +github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= +github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= +github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +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.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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +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/google/cel-go v0.28.0 h1:KjSWstCpz/MN5t4a8gnGJNIYUsJRpdi/r97xWDphIQc= +github.com/google/cel-go v0.28.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= +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.5/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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +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/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +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/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= +github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= +github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= +github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= +github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= @@ -20,62 +168,277 @@ github.com/lmittmann/tint v1.0.6 h1:vkkuDAZXc0EFGNzYjWcV0h7eEX+uujH48f/ifSkJWgc= github.com/lmittmann/tint v1.0.6/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -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/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= -github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs= +github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= +github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= +github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= +github.com/mfridman/xflag v0.1.0 h1:TWZrZwG1QklFX5S4j1vxfF1sZbZeZSGofMwPMLAF29M= +github.com/mfridman/xflag v0.1.0/go.mod h1:/483ywM5ZO5SuMVjrIGquYNE5CzLrj5Ux/LxWWnjRaE= +github.com/microsoft/go-mssqldb v1.9.8 h1:d4IFMvF/o+HdpXUqbBfzHvn/NlFA75YGcfHUUvDFJEM= +github.com/microsoft/go-mssqldb v1.9.8/go.mod h1:eGSRSGAW4hKMy5YcAenhCDjIRm2rhqIdmmwgciMzLus= +github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A= +github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= +github.com/ncruces/go-sqlite3 v0.32.0 h1:hNBUXp88LrfQCsuyXLqWTbTUG35sUuktDsqhhgHvU20= +github.com/ncruces/go-sqlite3 v0.32.0/go.mod h1:MIWTK60ONDl0oVY073zYvJP21C3Dly6P9bxVpgkLwdQ= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M= +github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g= +github.com/paulmach/orb v0.13.0 h1:r7n7mQGGF+cj/CbcivEj9J3HGK+XR+yXnvzRdq9saIw= +github.com/paulmach/orb v0.13.0/go.mod h1:6scRWINywA2Jf05dcjOfLfxrUIMECvTSG2MVbRLxu/k= +github.com/pganalyze/pg_query_go/v6 v6.2.2 h1:O0L6zMC226R82RF3X5n0Ki6HjytDsoAzuzp4ATVAHNo= +github.com/pganalyze/pg_query_go/v6 v6.2.2/go.mod h1:Cn6+j4870kJz3iYNsb0VsNG04vpSWgEvBwc590J4qD0= +github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY= +github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= +github.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pingcap/errors v0.11.5-0.20250523034308-74f78ae071ee h1:/IDPbpzkzA97t1/Z1+C3KlxbevjMeaI6BQYxvivu4u8= +github.com/pingcap/errors v0.11.5-0.20250523034308-74f78ae071ee/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg= +github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86 h1:tdMsjOqUR7YXHoBitzdebTvOjs/swniBTOLy5XiMtuE= +github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86/go.mod h1:exzhVYca3WRtd6gclGNErRWb1qEgff3LYta0LvRmON4= +github.com/pingcap/log v1.1.0 h1:ELiPxACz7vdo1qAvvaWJg1NrYFoY6gqAh/+Uo6aXdD8= +github.com/pingcap/log v1.1.0/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4= +github.com/pingcap/tidb/pkg/parser v0.0.0-20260418072757-ce92298d1124 h1:zYmP5fBH+i2yhhU6f5uOol6zxHtR2/sD47BsJLfy0oU= +github.com/pingcap/tidb/pkg/parser v0.0.0-20260418072757-ce92298d1124/go.mod h1:zDLDsfNBU5+L6T4J9/OgWAHc/WZvMUjbpgHqQ/t3yKo= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.8.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/pressly/goose/v3 v3.27.1 h1:6uEvcprBybDmW4hcz3gYujhARhye+GoWKhEWyzD5sh4= +github.com/pressly/goose/v3 v3.27.1/go.mod h1:maruOxsPnIG2yHHyo8UqKWXYKFcH7Q76csUV7+7KYoM= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= +github.com/rekby/fixenv v0.6.1 h1:jUFiSPpajT4WY2cYuc++7Y1zWrnCxnovGCIX72PZniM= +github.com/rekby/fixenv v0.6.1/go.mod h1:/b5LRc06BYJtslRtHKxsPWFT/ySpHV+rWvzTg+XWk4c= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/riza-io/grpc-go v0.2.0 h1:2HxQKFVE7VuYstcJ8zqpN84VnAoJ4dCL6YFhJewNcHQ= +github.com/riza-io/grpc-go v0.2.0/go.mod h1:2bDvR9KkKC3KhtlSHfR3dAXjUMT86kg4UfWFyVGWqi8= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= +github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= +github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/sqlc-dev/doubleclick v1.0.0 h1:2/OApfQ2eLgcfa/Fqs8WSMA6atH0G8j9hHbQIgMfAXI= +github.com/sqlc-dev/doubleclick v1.0.0/go.mod h1:ODHRroSrk/rr5neRHlWMSRijqOak8YmNaO3VAZCNl5Y= +github.com/sqlc-dev/sqlc v1.31.1 h1:+V+BjBJfFNPX/RFfL8eiZD9jk9lVJUEGGllWvnYNqbc= +github.com/sqlc-dev/sqlc v1.31.1/go.mod h1:6ZPww/Jd3G6MzJeW6NrqizjL+52vYNaaXP9yMeJ/Nao= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA= +github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU= +github.com/tursodatabase/libsql-client-go v0.0.0-20251219100830-236aa1ff8acc h1:lzi/5fg2EfinRlh3v//YyIhnc4tY7BTqazQGwb1ar+0= +github.com/tursodatabase/libsql-client-go v0.0.0-20251219100830-236aa1ff8acc/go.mod h1:08inkKyguB6CGGssc/JzhmQWwBgFQBgjlYFjxjRh7nU= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= -golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= -golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= -golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= -golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +github.com/vertica/vertica-sql-go v1.3.6 h1:uDJPdBivsI5EwfX3NMDWZaQlVs9zTnVyxE/nYhr3bY0= +github.com/vertica/vertica-sql-go v1.3.6/go.mod h1:jnn2GFuv+O2Jcjktb7zyc4Utlbu9YVqpHH/lx63+1M4= +github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 h1:mJdDDPblDfPe7z7go8Dvv1AJQDI3eQ/5xith3q2mFlo= +github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07/go.mod h1:Ak17IJ037caFp4jpCw/iQQ7/W74Sqpb1YuKJU6HTKfM= +github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 h1:OvLBa8SqJnZ6P+mjlzc2K7PM22rRUPE1x32G9DTPrC4= +github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52/go.mod h1:jMeV4Vpbi8osrE/pKUxRZkVaA0EX7NZN0A9/oRzgpgY= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/ydb-platform/ydb-go-genproto v0.0.0-20260311095541-ebbf792c1180 h1:avIdi8eGXjKbn1WLokNR1Ofnz1k8t7tJ88YQLD/iCi8= +github.com/ydb-platform/ydb-go-genproto v0.0.0-20260311095541-ebbf792c1180/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= +github.com/ydb-platform/ydb-go-sdk/v3 v3.135.0 h1:8c/M2B5W5xJd968DCedPv7DvQHuKzzhGGsO6J9x2+5U= +github.com/ydb-platform/ydb-go-sdk/v3 v3.135.0/go.mod h1:VYUUkRJkKuQPkIpgtZJj6+58Fa2g8ccAqdmaaK6HP5k= +github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +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/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +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.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= +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.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +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-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.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +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/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.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +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-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= 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.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/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.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/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= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +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-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 h1:vmC/ws+pLzWjj/gzApyoZuSVrDtF1aod4u/+bbj8hgM= +google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:p3MLuOwURrGBRoEyFHBT3GjUwaCQVKeNqqWxlcISGdw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 h1:XF8+t6QQiS0o9ArVan/HW8Q7cycNPGsJf6GA2nXxYAg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +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.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +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.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +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.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= -modernc.org/cc/v4 v4.24.2 h1:uektamHbSXU7egelXcyVpMaaAsrRH4/+uMKUQAQUdOw= -modernc.org/cc/v4 v4.24.2/go.mod h1:T1lKJZhXIi2VSqGBiB4LIbKs9NsKTbUXj4IDrmGqtTI= -modernc.org/ccgo/v4 v4.23.5 h1:6uAwu8u3pnla3l/+UVUrDDO1HIGxHTYmFH6w+X9nsyw= -modernc.org/ccgo/v4 v4.23.5/go.mod h1:FogrWfBdzqLWm1ku6cfr4IzEFouq2fSAPf6aSAHdAJQ= -modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= -modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= -modernc.org/gc/v2 v2.6.0 h1:Tiw3pezQj7PfV8k4Dzyu/vhRHR2e92kOXtTFU8pbCl4= -modernc.org/gc/v2 v2.6.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= -modernc.org/libc v1.61.6 h1:L2jW0wxHPCyHK0YSHaGaVlY0WxjpG/TTVdg6gRJOPqw= -modernc.org/libc v1.61.6/go.mod h1:G+DzuaCcReUYYg4nNSfigIfTDCENdj9EByglvaRx53A= +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= +howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= +howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= +howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= +modernc.org/cc/v4 v4.28.1 h1:XpLbkYVQ24E8tX5u8+yWGvaxerxkR/S4zqxI8ZoSBuc= +modernc.org/cc/v4 v4.28.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +modernc.org/ccgo/v4 v4.33.0 h1:dspBCm75jsj8Y/ufwAMVfe375L2iYdMyQ2QG/v3hL54= +modernc.org/ccgo/v4 v4.33.0/go.mod h1:+RhXBoRYzRwaH21mV/aj6XvQRDtfjcZfAlPMsQo8CR0= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo= +modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.72.1 h1:db1xwJ6u1kE3KHTFTTbe2GCrczHPKzlURP0aDC4NGD0= +modernc.org/libc v1.72.1/go.mod h1:HRMiC/PhPGLIPM7GzAFCbI+oSgE3dhZ8FWftmRrHVlY= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= -modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= -modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= -modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= -modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g= -modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE= -modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= -modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= +modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.49.1 h1:dYGHTKcX1sJ+EQDnUzvz4TJ5GbuvhNJa8Fg6ElGx73U= +modernc.org/sqlite v1.49.1/go.mod h1:m0w8xhwYUVY3H6pSDwc3gkJ/irZT/0YEXwBlhaxQEew= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/internal/handler/admin_api.go b/internal/handler/admin_api.go index 608de9e..fcbd541 100644 --- a/internal/handler/admin_api.go +++ b/internal/handler/admin_api.go @@ -461,14 +461,14 @@ func (h *Handler) APIUploadPageImage(c echo.Context) error { if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to process upload"}) } - defer src.Close() + defer func() { _ = src.Close() }() dstPath := filepath.Join(uploadDir, filename) dst, err := os.Create(dstPath) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to save file"}) } - defer dst.Close() + defer func() { _ = dst.Close() }() if _, err := io.Copy(dst, src); err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to save file"}) diff --git a/internal/notify/smtp.go b/internal/notify/smtp.go index a9e8920..669a836 100644 --- a/internal/notify/smtp.go +++ b/internal/notify/smtp.go @@ -98,13 +98,13 @@ func sendSMTP(ctx context.Context, smtpCfg config.SMTPConfig, to []string, messa if err != nil { return err } - defer conn.Close() + defer func() { _ = conn.Close() }() client, err := smtp.NewClient(conn, smtpCfg.Host) if err != nil { return err } - defer client.Quit() + defer func() { _ = client.Quit() }() return sendWithClient(client, auth, smtpCfg, to, message) } @@ -114,13 +114,13 @@ func sendSMTP(ctx context.Context, smtpCfg config.SMTPConfig, to []string, messa if err != nil { return err } - defer conn.Close() + defer func() { _ = conn.Close() }() client, err := smtp.NewClient(conn, smtpCfg.Host) if err != nil { return err } - defer client.Quit() + defer func() { _ = client.Quit() }() if ok, _ := client.Extension("STARTTLS"); ok { if err := client.StartTLS(&tls.Config{ From 3713b0b29bf1f995ac7cdd05e08f9848fbcf9880 Mon Sep 17 00:00:00 2001 From: Logan LaNou Date: Tue, 26 May 2026 21:28:44 -0500 Subject: [PATCH 2/5] feat: apply goose migrations automatically on startup internal/database.New now runs all pending goose migrations from an embedded filesystem (migrations/embed.go) on boot, so a fresh or out-of-date database is brought to schema without a separate `make migrate` step. This fixes the "no such table: site_settings" errors that occurred when the DB had never been migrated. `make migrate` remains for manual/CLI use. Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/database/database.go | 24 ++++++++++++++++++++++++ migrations/embed.go | 9 +++++++++ 2 files changed, 33 insertions(+) create mode 100644 migrations/embed.go diff --git a/internal/database/database.go b/internal/database/database.go index a5455c8..0569989 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -7,9 +7,11 @@ import ( "os" "path/filepath" + "github.com/pressly/goose/v3" _ "modernc.org/sqlite" "rowetech/internal/database/sqlc" + "rowetech/migrations" ) type DB struct { @@ -35,12 +37,34 @@ func New(ctx context.Context, databasePath string) (*DB, error) { return nil, fmt.Errorf("unable to ping database: %w", err) } + if err := runMigrations(conn); err != nil { + return nil, fmt.Errorf("unable to run migrations: %w", err) + } + return &DB{ Conn: conn, Queries: sqlc.New(conn), }, nil } +// runMigrations applies any pending goose migrations from the embedded +// filesystem, so a fresh or out-of-date database is brought up to schema on +// startup without a separate `make migrate` step. +func runMigrations(conn *sql.DB) error { + goose.SetBaseFS(migrations.FS) + defer goose.SetBaseFS(nil) + + if err := goose.SetDialect("sqlite3"); err != nil { + return fmt.Errorf("failed to set goose dialect: %w", err) + } + + if err := goose.Up(conn, "."); err != nil { + return fmt.Errorf("failed to apply migrations: %w", err) + } + + return nil +} + func (db *DB) Close() error { return db.Conn.Close() } diff --git a/migrations/embed.go b/migrations/embed.go new file mode 100644 index 0000000..d803e7a --- /dev/null +++ b/migrations/embed.go @@ -0,0 +1,9 @@ +// Package migrations holds the goose SQL migrations and exposes them as an +// embedded filesystem so they can be applied automatically on server startup +// (see internal/database.New) as well as via the goose CLI (`make migrate`). +package migrations + +import "embed" + +//go:embed *.sql +var FS embed.FS From aedb73254f93f7fc60e6fd917b1d65f1dbb66f86 Mon Sep 17 00:00:00 2001 From: Logan LaNou Date: Tue, 26 May 2026 21:28:44 -0500 Subject: [PATCH 3/5] feat: fall back to next free port and log startup access URLs If the configured PORT is in use, the server now binds the next available port (internal/portutil.FindAvailable, mirrored from gopherguides/hype) and logs a warning. It also logs clickable access URLs at startup: localhost and, when TAILSCALE_HOSTNAME is set, the Tailscale magic-DNS host (reachable from any tailnet device). Co-Authored-By: Claude Opus 4.7 (1M context) --- .envrc.example | 5 +++ cmd/server/main.go | 18 +++++++++-- internal/config/config.go | 8 +++-- internal/portutil/portutil.go | 57 +++++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 internal/portutil/portutil.go diff --git a/.envrc.example b/.envrc.example index 2122488..2afd47b 100644 --- a/.envrc.example +++ b/.envrc.example @@ -9,6 +9,11 @@ export PORT="3000" export ENV="development" export LOG_LEVEL="DEBUG" +# Tailscale magic-DNS host for this machine (e.g. prometheus.tail60aac7.ts.net). +# When set, the server logs a clickable http://: access URL at +# startup so the dev server is reachable from any device on your tailnet. +export TAILSCALE_HOSTNAME="" + # Site / SEO (used for meta tags, OG, etc.) export SITE_NAME="RoweTech Machine & Engineering" export SITE_URL="http://localhost:3000" diff --git a/cmd/server/main.go b/cmd/server/main.go index 44dc074..bfaa180 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -5,6 +5,7 @@ import ( "log/slog" "os" "os/signal" + "strings" "syscall" "time" @@ -12,6 +13,7 @@ import ( "rowetech/internal/database" "rowetech/internal/handler" "rowetech/internal/middleware" + "rowetech/internal/portutil" "github.com/labstack/echo/v4" ) @@ -40,9 +42,21 @@ func main() { h := handler.New(cfg, db) h.RegisterRoutes(e) + // Bind the configured port, falling back to the next free port when it's + // already in use so `make dev` doesn't die on a stray process. + addr, triedPorts := portutil.FindAvailable(":" + cfg.Port) + if len(triedPorts) > 0 { + slog.Warn("configured port in use, using next free port", + "configured", cfg.Port, "tried", strings.Join(triedPorts, ","), "using", strings.TrimPrefix(addr, ":")) + } + port := strings.TrimPrefix(addr, ":") + go func() { - addr := ":" + cfg.Port - slog.Info("starting server", "port", cfg.Port, "env", cfg.Env) + slog.Info("starting server", "port", port, "env", cfg.Env) + slog.Info("access URL", "url", "http://localhost:"+port) + if cfg.TailscaleHostname != "" { + slog.Info("access URL", "url", "http://"+cfg.TailscaleHostname+":"+port, "via", "tailscale") + } if err := e.Start(addr); err != nil { slog.Info("shutting down server") } diff --git a/internal/config/config.go b/internal/config/config.go index 927993b..b736f60 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -28,6 +28,7 @@ type Config struct { DatabaseURL string Port string Env string + TailscaleHostname string Site SiteConfig ClerkSecretKey string ClerkPublishableKey string @@ -54,9 +55,10 @@ func Load() *Config { } cfg := &Config{ - DatabaseURL: getEnvOrDefault("DATABASE_URL", "./data/rowetech.db"), - Port: getEnvOrDefault("PORT", "3000"), - Env: getEnvOrDefault("ENV", "development"), + DatabaseURL: getEnvOrDefault("DATABASE_URL", "./data/rowetech.db"), + Port: getEnvOrDefault("PORT", "3000"), + Env: getEnvOrDefault("ENV", "development"), + TailscaleHostname: strings.TrimSpace(os.Getenv("TAILSCALE_HOSTNAME")), Site: SiteConfig{ Name: getEnvOrDefault("SITE_NAME", "RoweTech Machine & Engineering"), URL: getEnvOrDefault("SITE_URL", "http://localhost:3000"), diff --git a/internal/portutil/portutil.go b/internal/portutil/portutil.go new file mode 100644 index 0000000..4ed17fe --- /dev/null +++ b/internal/portutil/portutil.go @@ -0,0 +1,57 @@ +package portutil + +import ( + "fmt" + "net" + "strconv" + "strings" +) + +// FindAvailable finds an available port starting from the given address. +// It returns the available address and a list of ports that were tried but in use. +// The addr parameter should be in the format ":port" (e.g., ":3000"). +func FindAvailable(addr string) (string, []string) { + var triedPorts []string + + port := 3000 + if strings.HasPrefix(addr, ":") { + if p, err := strconv.Atoi(addr[1:]); err == nil { + port = p + } + } + + maxAttempts := 100 + for i := 0; i < maxAttempts; i++ { + testAddr := fmt.Sprintf(":%d", port) + ln, err := net.Listen("tcp", testAddr) + if err == nil { + _ = ln.Close() + return testAddr, triedPorts + } + triedPorts = append(triedPorts, strconv.Itoa(port)) + port++ + } + + return fmt.Sprintf(":%d", port), triedPorts +} + +// FindAvailablePort is a convenience function that takes an int port +// and returns the available port number. +func FindAvailablePort(startPort int) (int, []int) { + var triedPorts []int + + port := startPort + maxAttempts := 100 + for i := 0; i < maxAttempts; i++ { + testAddr := fmt.Sprintf(":%d", port) + ln, err := net.Listen("tcp", testAddr) + if err == nil { + _ = ln.Close() + return port, triedPorts + } + triedPorts = append(triedPorts, port) + port++ + } + + return port, triedPorts +} From f628ee2147712ca4c355ee78db5ca2aba7e335da Mon Sep 17 00:00:00 2001 From: Logan LaNou Date: Tue, 26 May 2026 21:28:57 -0500 Subject: [PATCH 4/5] feat: dev-only loopback auth bypass for admin testing Under `-tags dev` (which `make dev` builds via .air.toml), register loopback-only GET /auth/dev/login and POST /auth/dev/logout so admin pages can be tested without Clerk. RequireAdminAccess honors the dev session cookie before any Clerk verification. Safety (cannot run in production): - //go:build dev gates auth_dev.go; auth_dev_stub.go (!dev) is a no-op. - make build / build-static omit -tags dev, compiling it out entirely. - Loopback-only: both RemoteAddr and Host must be loopback (defeats X-Forwarded-For spoofing and reverse-proxy bypass). - init() fatals under ENV/ENVIRONMENT=production. - auth_dev_prod_test.go asserts no /auth/dev/* routes in production builds. Documents the workflow in CLAUDE.md and a new AGENTS.md so Claude/Codex know how to test admin/dashboard pages going forward. Co-Authored-By: Claude Opus 4.7 (1M context) --- AGENTS.md | 62 +++++++++ CLAUDE.md | 92 +++++++++++-- internal/handler/handler.go | 5 +- internal/middleware/auth_dev.go | 151 ++++++++++++++++++++++ internal/middleware/auth_dev_dev_test.go | 98 ++++++++++++++ internal/middleware/auth_dev_prod_test.go | 42 ++++++ internal/middleware/auth_dev_stub.go | 19 +++ internal/middleware/clerk.go | 10 ++ 8 files changed, 467 insertions(+), 12 deletions(-) create mode 100644 AGENTS.md create mode 100644 internal/middleware/auth_dev.go create mode 100644 internal/middleware/auth_dev_dev_test.go create mode 100644 internal/middleware/auth_dev_prod_test.go create mode 100644 internal/middleware/auth_dev_stub.go diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..37b5063 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,62 @@ +# Agent Guide — RoweTech Machine & Engineering + +This file is for AI coding agents (Claude Code, Codex, etc.). The full project +guide is **[CLAUDE.md](./CLAUDE.md)** — read it. This file highlights the rules +that most often trip agents up. + +## Build / dev loop + +- `make dev` is usually already running (hot reload via Air). It builds with + `-tags dev`, regenerates templ + sqlc, runs `go mod tidy`, and restarts. +- **After any change, check `./tmp/air-combined.log`** for compile / templ / sqlc + errors. Never assume a change built. +- Do **not** manually run `templ generate`, `sqlc generate`, `go build`, or `air`. + +## Tooling is pinned via `go tool` (do not reintroduce global installs) + +`templ`, `sqlc`, and `goose` are pinned in `go.mod` via the `tool` directive and +invoked as `go tool templ` / `go tool sqlc` / `go tool goose`. This keeps the +generator version in lockstep with the runtime — relying on globally-installed +binaries caused build failures like `undefined: templ.ResolveAttributeValue`. +Bump templ with `make templ-update`. + +## Database migrations run on startup + +`internal/database.New` applies all pending goose migrations from the embedded +`migrations` package on boot. A fresh DB is migrated automatically; you don't +need `make migrate`. After adding a migration, just restart `make dev`. + +## Testing admin / dashboard pages — Dev Auth Bypass + +Admin pages (`/admin/*`) are Clerk-gated, but the `-tags dev` build (used by +`make dev`) compiles in a **loopback-only** bypass so you can test them: + +```bash +# 302 = dev tag live; 404 = restart `make dev` (air didn't rebuild with -tags dev) +curl -s -o /dev/null -w "%{http_code}\n" "http://localhost:3000/auth/dev/login" + +# Log in as admin and exercise an admin page: +JAR=/tmp/rowetech.jar +curl -sc "$JAR" -o /dev/null "http://localhost:3000/auth/dev/login?return_to=/admin" +curl -s -b "$JAR" -o /dev/null -w "%{http_code}\n" "http://localhost:3000/admin" # 200 +``` + +In a browser (or Chrome DevTools MCP), navigate to +`http://localhost:3000/auth/dev/login?return_to=/admin` to land authenticated in +the admin panel. Optional `?email=` impersonates a specific admin. + +**Never weaken these safety properties** when editing the bypass: +- It lives behind `//go:build dev` (`internal/middleware/auth_dev.go`); the + `//go:build !dev` stub keeps it out of production binaries. +- Production builds (`make build`, `make build-static`) omit `-tags dev`. +- It is loopback-only (checks both `RemoteAddr` and `Host`) and `init()` fatals + under `ENV=production`. +- `make test-dev` runs the `-tags dev` tests; `auth_dev_prod_test.go` asserts the + routes are absent from production builds. Keep both green. + +## Conventions + +- Logging: `slog` only (never `fmt.Printf` / `log.Printf`). +- Errors: wrap with context — `fmt.Errorf("...: %w", err)`. +- DB access: sqlc-generated queries (`h.db.Queries.*`). +- Templates own their meta tags via `meta.New(...)`. diff --git a/CLAUDE.md b/CLAUDE.md index 7afc2e2..f9da2e1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,13 +15,80 @@ Never assume code changes succeeded without checking this log. `make dev` is always running during development. It automatically: 1. Kills existing process on port -2. Regenerates Templ templates -3. Regenerates sqlc queries +2. Regenerates Templ templates (`go tool templ generate`) +3. Regenerates sqlc queries (`go tool sqlc generate`) 4. Runs go mod tidy -5. Rebuilds and restarts server +5. Rebuilds (with `-tags dev`) and restarts server **You do NOT need to manually run:** `templ generate`, `sqlc generate`, `go build`, or `air`. +### Tooling is pinned via `go tool` + +`templ`, `sqlc`, and `goose` are pinned in `go.mod` via the `tool` directive and +invoked everywhere as `go tool templ` / `go tool sqlc` / `go tool goose`. This +guarantees the generator version always matches the runtime version — do **not** +rely on globally-installed `templ`/`sqlc`/`goose` binaries (version drift there +caused real build failures, e.g. `undefined: templ.ResolveAttributeValue`). To +bump templ safely, run `make templ-update` (bumps runtime + tool pin together). + +### Migrations run automatically on startup + +`internal/database.New` applies all pending goose migrations from the embedded +`migrations` package (`migrations/embed.go`) on every boot. A fresh or +out-of-date DB is brought to schema automatically — you do **not** need to run +`make migrate` by hand for the dev server. `make migrate` still exists for +manual/CLI use. + +### Port fallback + +If the configured `PORT` is in use, the server binds the next free port (see +`internal/portutil`) and logs `configured port in use, using next free port`. +It also logs clickable `access URL` lines at startup (localhost + the Tailscale +host from `TAILSCALE_HOSTNAME`, if set). + +## Dev-Only Auth Bypass (for testing admin/dashboard pages) + +Admin pages (`/admin/*`) are normally gated by Clerk. To test them locally +without Clerk, `make dev` builds with `-tags dev` (see `.air.toml`), which +compiles in two **loopback-only** routes: + +- `GET /auth/dev/login[?email=][&return_to=/path]` — sets the `dev_admin` + session cookie (impersonating `email`, defaulting to the first + `ADMIN_EMAILS`/`ADMIN_EMAIL` entry) and redirects to `return_to` (default + `/admin`). `RequireAdminAccess` honors this cookie and skips Clerk. +- `POST /auth/dev/logout` — clears the cookie. + +**How to use it (curl):** + +```bash +# Is the dev tag live? 302 = yes, 404 = air built without -tags dev (restart make dev) +curl -s -o /dev/null -w "%{http_code}\n" "http://localhost:3000/auth/dev/login" + +# Log in as admin, then hit an admin page with the saved cookie: +JAR=/tmp/rowetech.jar +curl -sc $JAR -o /dev/null "http://localhost:3000/auth/dev/login?return_to=/admin" +curl -s -b $JAR -o /dev/null -w "%{http_code}\n" "http://localhost:3000/admin" # 200 +``` + +In a browser, just visit `http://localhost:3000/auth/dev/login` — it sets the +cookie and redirects you into `/admin`. + +**Safety (why this can never run in production):** +- `//go:build dev` — `internal/middleware/auth_dev.go` is compiled out of + production binaries entirely; `auth_dev_stub.go` (`//go:build !dev`) provides + no-op `RegisterDevAuthRoutes` / `DevAuthEnabled()=false` / `devAdminBypass`. +- `make build` and `make build-static` (production) **omit** `-tags dev`. +- Loopback-only: both the raw TCP peer (`RemoteAddr`, not `RealIP`) and the + `Host` header must be loopback, or the request gets 403. Defeats + `X-Forwarded-For` spoofing and reverse-proxy bypass. +- `init()` in `auth_dev.go` fatals if `ENV`/`ENVIRONMENT=production`. +- `internal/middleware/auth_dev_prod_test.go` (`//go:build !dev`) asserts no + `/auth/dev/*` routes exist in a production build. Run dev-tagged tests with + `make test-dev`. + +**Gotcha:** Air does not reload `.air.toml` on its own. If the dev login route +returns 404, the running `air` was built without `-tags dev` — restart `make dev`. + ## Environment Setup All configuration via `.envrc` (copy from `.envrc.example`): @@ -33,6 +100,7 @@ export ENV="development" export LOG_LEVEL="DEBUG" export SITE_NAME="RoweTech Machine & Engineering" export SITE_URL="http://localhost:3000" +export TAILSCALE_HOSTNAME="your-machine.tailnet.ts.net" # optional; logged as a clickable startup URL export CLERK_SECRET_KEY="sk_test_..." export CLERK_PUBLISHABLE_KEY="pk_test_..." ``` @@ -41,15 +109,17 @@ export CLERK_PUBLISHABLE_KEY="pk_test_..." | Command | Description | |---------|-------------| -| `make dev` | Start with hot reload (main workflow) | -| `make build` | Build production binary | +| `make dev` | Start with hot reload, built with `-tags dev` (main workflow) | +| `make build` | Build production binary (no dev tag) | | `make test` | Run tests with race detection | +| `make test-dev` | Run tests including `-tags dev` (auth bypass) | | `make lint` | Run linters | -| `make generate` | Generate templ and sqlc code | +| `make generate` | Generate templ and sqlc code (via `go tool`) | | `make css` | Build Tailwind CSS | | `make css-watch` | Watch Tailwind (separate terminal) | -| `make migrate` | Run database migrations | -| `make setup` | Install development tools | +| `make migrate` | Run database migrations manually (also auto-run on startup) | +| `make templ-update` | Bump templ runtime + tool pin together | +| `make setup` | Install dev tools (air, golangci-lint; templ/sqlc/goose come via `go tool`) | ## Project Structure @@ -138,8 +208,7 @@ Use `hx-*` attributes for dynamic updates: 3. Run `make setup` to install tools 4. Copy `.envrc.example` to `.envrc` and configure 5. Run `direnv allow` (or source .envrc) -6. Run `make migrate` to set up database -7. Run `make dev` to start development server +6. Run `make dev` — the database is migrated automatically on startup ## Adding a New Page @@ -153,5 +222,6 @@ Use `hx-*` attributes for dynamic updates: 1. Create migration: `make migrate-create NAME=create_tablename` 2. Edit migration file in `migrations/` 3. Add queries in `sqlc/queries/` -4. Run `make migrate` +4. Restart `make dev` — pending migrations apply automatically on startup + (embedded via `migrations/embed.go`). `make migrate` is only for manual/CLI use. 5. sqlc will regenerate on next build diff --git a/internal/handler/handler.go b/internal/handler/handler.go index f71238e..12e026f 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -54,9 +54,12 @@ func (h *Handler) RegisterRoutes(e *echo.Echo) { return c.Redirect(http.StatusFound, "/admin") }) + // Dev-only auth bypass (loopback-only; compiled out of production builds). + middleware.RegisterDevAuthRoutes(e, h.cfg) + // Admin routes admin := e.Group("/admin") - if h.cfg.HasClerk() { + if h.cfg.HasClerk() || middleware.DevAuthEnabled() { admin.Use(middleware.RequireAdminAccess(h.cfg)) } admin.GET("", h.AdminDashboard) diff --git a/internal/middleware/auth_dev.go b/internal/middleware/auth_dev.go new file mode 100644 index 0000000..eb7b7e8 --- /dev/null +++ b/internal/middleware/auth_dev.go @@ -0,0 +1,151 @@ +//go:build dev + +package middleware + +import ( + "log/slog" + "net" + "net/http" + "os" + "strings" + + "rowetech/internal/config" + + "github.com/labstack/echo/v4" +) + +// devSessionCookie marks an impersonated admin in dev builds. Its value is the +// admin email being impersonated. It is only ever honored on loopback requests +// (see devAdminBypass) and only exists in binaries built with `-tags dev`. +const devSessionCookie = "dev_admin" + +// init is a last-resort runtime guard. The `-tags dev` code should never run in +// production, but if a dev-tagged binary is ever started with ENV=production we +// refuse to boot rather than silently exposing the auth bypass. +func init() { + if os.Getenv("ENV") == "production" || os.Getenv("ENVIRONMENT") == "production" { + slog.Error("FATAL: binary built with -tags dev is running with ENV=production; refusing to start") + os.Exit(1) + } +} + +// DevAuthEnabled reports whether the dev auth bypass is compiled in. True only +// under `-tags dev`. Used by route registration to decide whether to attach the +// admin gate even when Clerk is not configured. +func DevAuthEnabled() bool { return true } + +// RegisterDevAuthRoutes wires the loopback-only dev login/logout endpoints. +// It is a no-op in production builds (see auth_dev_stub.go). +func RegisterDevAuthRoutes(e *echo.Echo, cfg *config.Config) { + e.GET("/auth/dev/login", handleDevLogin(cfg)) + e.POST("/auth/dev/logout", handleDevLogout()) + slog.Warn("DEV AUTH BYPASS routes registered: GET /auth/dev/login, POST /auth/dev/logout — never deploy this binary to production") +} + +// handleDevLogin impersonates an admin by setting the dev session cookie, then +// redirects to return_to (default /admin). Loopback-only. +// +// GET /auth/dev/login[?email=][&return_to=/path] +// +// With no email, the first configured admin email is used. +func handleDevLogin(cfg *config.Config) echo.HandlerFunc { + return func(c echo.Context) error { + if !isLoopbackRequest(c) { + slog.Warn("DEV AUTH BYPASS: rejecting non-loopback caller", + "remote_addr", c.Request().RemoteAddr, "host", c.Request().Host) + return c.JSON(http.StatusForbidden, map[string]string{"error": "/auth/dev/login is restricted to loopback callers"}) + } + + email := strings.TrimSpace(c.QueryParam("email")) + if email == "" { + email = defaultDevAdminEmail(cfg) + } + + c.SetCookie(&http.Cookie{ + Name: devSessionCookie, + Value: email, + Path: "/", + HttpOnly: true, + Secure: false, + SameSite: http.SameSiteLaxMode, + MaxAge: 7 * 24 * 60 * 60, + }) + + slog.Warn("DEV AUTH BYPASS", "email", email, "remote_addr", c.Request().RemoteAddr) + return c.Redirect(http.StatusFound, sanitizeReturnTo(c.QueryParam("return_to"))) + } +} + +// handleDevLogout clears the dev session cookie. Loopback-only. +func handleDevLogout() echo.HandlerFunc { + return func(c echo.Context) error { + if !isLoopbackRequest(c) { + return c.JSON(http.StatusForbidden, map[string]string{"error": "/auth/dev/logout is restricted to loopback callers"}) + } + c.SetCookie(&http.Cookie{ + Name: devSessionCookie, + Value: "", + Path: "/", + HttpOnly: true, + MaxAge: -1, + }) + return c.JSON(http.StatusOK, map[string]string{"status": "logged out"}) + } +} + +// devAdminBypass reports the impersonated admin email when a valid dev session +// is present on a loopback request. RequireAdminAccess calls this before any +// Clerk verification so dev builds can reach admin pages without Clerk. +func devAdminBypass(c echo.Context, _ *config.Config) (string, bool) { + if !isLoopbackRequest(c) { + return "", false + } + cookie, err := c.Cookie(devSessionCookie) + if err != nil || cookie.Value == "" { + return "", false + } + return cookie.Value, true +} + +func defaultDevAdminEmail(cfg *config.Config) string { + if cfg != nil && len(cfg.AdminEmails) > 0 { + return cfg.AdminEmails[0] + } + return "dev-admin@lanou.com" +} + +// sanitizeReturnTo only allows same-origin absolute paths, defaulting to /admin. +func sanitizeReturnTo(raw string) string { + if strings.HasPrefix(raw, "/") && !strings.HasPrefix(raw, "//") { + return raw + } + return "/admin" +} + +// isLoopbackRequest requires BOTH the raw TCP peer and the Host header to be +// loopback. RemoteAddr (not RealIP) defeats X-Forwarded-For spoofing; the Host +// check defeats a reverse-proxy that forwards a public hostname to localhost. +func isLoopbackRequest(c echo.Context) bool { + return isRemoteLoopback(c.Request().RemoteAddr) && isHostLoopback(c.Request().Host) +} + +func isRemoteLoopback(remoteAddr string) bool { + host, _, err := net.SplitHostPort(remoteAddr) + if err != nil { + host = remoteAddr + } + ip := net.ParseIP(host) + return ip != nil && ip.IsLoopback() +} + +func isHostLoopback(hostHeader string) bool { + host := hostHeader + if h, _, err := net.SplitHostPort(host); err == nil { + host = h + } + if host == "localhost" { + return true + } + ip := net.ParseIP(host) + return ip != nil && ip.IsLoopback() +} diff --git a/internal/middleware/auth_dev_dev_test.go b/internal/middleware/auth_dev_dev_test.go new file mode 100644 index 0000000..5e038db --- /dev/null +++ b/internal/middleware/auth_dev_dev_test.go @@ -0,0 +1,98 @@ +//go:build dev + +package middleware + +import ( + "net/http" + "net/http/httptest" + "testing" + + "rowetech/internal/config" + + "github.com/labstack/echo/v4" +) + +func newCtx(t *testing.T, remoteAddr, host, cookieVal string) echo.Context { + t.Helper() + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/admin", nil) + req.RemoteAddr = remoteAddr + if host != "" { + req.Host = host + } + if cookieVal != "" { + req.AddCookie(&http.Cookie{Name: devSessionCookie, Value: cookieVal}) + } + return e.NewContext(req, httptest.NewRecorder()) +} + +func TestDevAuthRoutesRegistered(t *testing.T) { + e := echo.New() + RegisterDevAuthRoutes(e, &config.Config{}) + var gotLogin, gotLogout bool + for _, r := range e.Routes() { + switch { + case r.Method == http.MethodGet && r.Path == "/auth/dev/login": + gotLogin = true + case r.Method == http.MethodPost && r.Path == "/auth/dev/logout": + gotLogout = true + } + } + if !gotLogin || !gotLogout { + t.Fatalf("dev auth routes missing: login=%v logout=%v", gotLogin, gotLogout) + } +} + +func TestDevAdminBypassLoopbackGating(t *testing.T) { + cfg := &config.Config{} + tests := []struct { + name string + remoteAddr string + host string + cookie string + wantOK bool + }{ + {"loopback with cookie", "127.0.0.1:5000", "localhost:3000", "admin@x.com", true}, + {"ipv6 loopback with cookie", "[::1]:5000", "[::1]:3000", "admin@x.com", true}, + {"loopback no cookie", "127.0.0.1:5000", "localhost:3000", "", false}, + {"remote peer with cookie", "10.0.0.5:5000", "localhost:3000", "admin@x.com", false}, + {"public host header with cookie", "127.0.0.1:5000", "rowetech.com", "admin@x.com", false}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + c := newCtx(t, tc.remoteAddr, tc.host, tc.cookie) + email, ok := devAdminBypass(c, cfg) + if ok != tc.wantOK { + t.Fatalf("devAdminBypass ok = %v, want %v (email=%q)", ok, tc.wantOK, email) + } + if ok && email != tc.cookie { + t.Fatalf("devAdminBypass email = %q, want %q", email, tc.cookie) + } + }) + } +} + +func TestSanitizeReturnTo(t *testing.T) { + cases := map[string]string{ + "": "/admin", + "/admin/contacts": "/admin/contacts", + "//evil.com": "/admin", + "https://evil.com": "/admin", + "/content?tab=home": "/content?tab=home", + } + for in, want := range cases { + if got := sanitizeReturnTo(in); got != want { + t.Errorf("sanitizeReturnTo(%q) = %q, want %q", in, got, want) + } + } +} + +func TestDefaultDevAdminEmail(t *testing.T) { + if got := defaultDevAdminEmail(&config.Config{}); got != "dev-admin@lanou.com" { + t.Errorf("default with no admins = %q", got) + } + cfg := &config.Config{AdminEmails: []string{"boss@rowetech.com"}} + if got := defaultDevAdminEmail(cfg); got != "boss@rowetech.com" { + t.Errorf("default with admins = %q, want boss@rowetech.com", got) + } +} diff --git a/internal/middleware/auth_dev_prod_test.go b/internal/middleware/auth_dev_prod_test.go new file mode 100644 index 0000000..15e2e41 --- /dev/null +++ b/internal/middleware/auth_dev_prod_test.go @@ -0,0 +1,42 @@ +//go:build !dev + +package middleware + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/labstack/echo/v4" +) + +// TestProductionBuildHasNoDevAuthRoutes asserts the dev login/logout routes are +// never registered in a production build (no -tags dev). +func TestProductionBuildHasNoDevAuthRoutes(t *testing.T) { + e := echo.New() + RegisterDevAuthRoutes(e, nil) + for _, r := range e.Routes() { + if strings.HasPrefix(r.Path, "/auth/dev") { + t.Fatalf("production build registered dev-only route %s %s", r.Method, r.Path) + } + } +} + +// TestDevAuthDisabledInProduction asserts the bypass is inert in production +// even when a dev session cookie is present. +func TestDevAuthDisabledInProduction(t *testing.T) { + if DevAuthEnabled() { + t.Fatal("DevAuthEnabled() must be false in a non-dev build") + } + + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/admin", nil) + req.RemoteAddr = "127.0.0.1:12345" + req.AddCookie(&http.Cookie{Name: "dev_admin", Value: "attacker@example.com"}) + c := e.NewContext(req, httptest.NewRecorder()) + + if email, ok := devAdminBypass(c, nil); ok { + t.Fatalf("devAdminBypass granted access in production build (email=%q)", email) + } +} diff --git a/internal/middleware/auth_dev_stub.go b/internal/middleware/auth_dev_stub.go new file mode 100644 index 0000000..961b19b --- /dev/null +++ b/internal/middleware/auth_dev_stub.go @@ -0,0 +1,19 @@ +//go:build !dev + +package middleware + +import ( + "rowetech/internal/config" + + "github.com/labstack/echo/v4" +) + +// DevAuthEnabled is always false in production builds. +func DevAuthEnabled() bool { return false } + +// RegisterDevAuthRoutes is a no-op in production builds, so callers can invoke +// it unconditionally without the dev login/logout routes ever existing. +func RegisterDevAuthRoutes(_ *echo.Echo, _ *config.Config) {} + +// devAdminBypass never grants access in production builds. +func devAdminBypass(_ echo.Context, _ *config.Config) (string, bool) { return "", false } diff --git a/internal/middleware/clerk.go b/internal/middleware/clerk.go index 48bff41..8b03482 100644 --- a/internal/middleware/clerk.go +++ b/internal/middleware/clerk.go @@ -15,6 +15,7 @@ import ( "time" "rowetech/internal/config" + "rowetech/internal/ctxkeys" "github.com/golang-jwt/jwt/v5" "github.com/labstack/echo/v4" @@ -36,6 +37,15 @@ func RequireAdminAccess(cfg *config.Config) echo.MiddlewareFunc { return c.Redirect(http.StatusFound, "/admin") } + // Dev-only impersonation: under `-tags dev`, a loopback request with + // a valid dev session cookie is treated as the named admin without + // Clerk. Compiled out of production builds (see auth_dev_stub.go). + if email, ok := devAdminBypass(c, cfg); ok { + ctx := context.WithValue(c.Request().Context(), ctxkeys.User, email) + c.SetRequest(c.Request().WithContext(ctx)) + return next(c) + } + if !cfg.HasClerk() { return next(c) } From eb503f49c0c36b33a7c55f40f3a12e68cfdc632b Mon Sep 17 00:00:00 2001 From: Logan LaNou Date: Tue, 26 May 2026 21:32:19 -0500 Subject: [PATCH 5/5] ci: bump golangci-lint to v2.10.1 (built with go1.26) v2.1.6 is built with go1.24 and refuses to lint a module targeting go 1.26 ("language version (go1.24) ... lower than targeted (1.26.0)"). v2.10.1 is built with go1.26.1. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c00c859..7d55e7a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,9 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v9 with: - version: v2.1.6 + # Must be built with Go >= the module's go directive (1.26); older + # builds error: "language version (go1.24) ... lower than targeted (1.26.0)". + version: v2.10.1 args: --timeout=5m - name: Run tests run: go test -race ./...