diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ff11bd7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +.git +.github +.env +.env.* +!.env.example +docs +tests +uploads +micrach +*.out +*.test diff --git a/.env.example b/.env.example index b56815a..61055a3 100644 --- a/.env.example +++ b/.env.example @@ -1,13 +1,13 @@ ENV=development PORT=3000 -IS_DB_SEEDED=true +IS_DB_SEEDED=false IS_RATE_LIMITER_ENABLED=true THREADS_MAX_COUNT=50 -POSTGRES_URL=postgres://localhost/micrach?pool_max_conns=5 +POSTGRES_URL=postgres://micrach:micrach@localhost:5432/micrach?pool_max_conns=5 THREAD_BUMP_LIMIT=500 IS_CAPTCHA_ACTIVE=true -GATEWAY_URL=http://localhost:3001 -GATEWAY_API_KEY=example -GATEWAY_BOARD_ID=b -GATEWAY_BOARD_URL=http://localhost:3000 -GATEWAY_BOARD_DESCRIPTION=Random \ No newline at end of file +GATEWAY_URL= +GATEWAY_API_KEY= +GATEWAY_BOARD_ID= +GATEWAY_BOARD_URL= +GATEWAY_BOARD_DESCRIPTION= diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..22e9ef9 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +version: 2 +updates: + - package-ecosystem: gomod + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 5 + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 5 diff --git a/.github/workflows/codesee-arch-diagram.yml b/.github/workflows/codesee-arch-diagram.yml deleted file mode 100644 index f02cc13..0000000 --- a/.github/workflows/codesee-arch-diagram.yml +++ /dev/null @@ -1,87 +0,0 @@ -on: - push: - branches: - - master - pull_request_target: - types: [opened, synchronize, reopened] - -name: CodeSee Map - -jobs: - test_map_action: - runs-on: ubuntu-latest - continue-on-error: true - name: Run CodeSee Map Analysis - steps: - - name: checkout - id: checkout - uses: actions/checkout@v2 - with: - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.ref }} - fetch-depth: 0 - - # codesee-detect-languages has an output with id languages. - - name: Detect Languages - id: detect-languages - uses: Codesee-io/codesee-detect-languages-action@latest - - - name: Configure JDK 16 - uses: actions/setup-java@v2 - if: ${{ fromJSON(steps.detect-languages.outputs.languages).java }} - with: - java-version: '16' - distribution: 'zulu' - - # CodeSee Maps Go support uses a static binary so there's no setup step required. - - - name: Configure Node.js 14 - uses: actions/setup-node@v2 - if: ${{ fromJSON(steps.detect-languages.outputs.languages).javascript }} - with: - node-version: '14' - - - name: Configure Python 3.x - uses: actions/setup-python@v2 - if: ${{ fromJSON(steps.detect-languages.outputs.languages).python }} - with: - python-version: '3.10' - architecture: 'x64' - - - name: Configure Ruby '3.x' - uses: ruby/setup-ruby@v1 - if: ${{ fromJSON(steps.detect-languages.outputs.languages).ruby }} - with: - ruby-version: '3.0' - - # We need the rust toolchain because it uses rustc and cargo to inspect the package - - name: Configure Rust 1.x stable - uses: actions-rs/toolchain@v1 - if: ${{ fromJSON(steps.detect-languages.outputs.languages).rust }} - with: - toolchain: stable - - - name: Generate Map - id: generate-map - uses: Codesee-io/codesee-map-action@latest - with: - step: map - api_token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} - github_ref: ${{ github.ref }} - languages: ${{ steps.detect-languages.outputs.languages }} - - - name: Upload Map - id: upload-map - uses: Codesee-io/codesee-map-action@latest - with: - step: mapUpload - api_token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} - github_ref: ${{ github.ref }} - - - name: Insights - id: insights - uses: Codesee-io/codesee-map-action@latest - with: - step: insights - api_token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} - github_ref: ${{ github.ref }} diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 3337e85..54bcd5f 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -1,76 +1,48 @@ -name: Push -# TODO: Need to be changed into separate configs after -on: [push, release] +name: CI + +on: + push: + pull_request: + +permissions: + contents: read jobs: - build: + verify: runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Install Go - uses: actions/setup-go@v2 - with: - go-version: '1.17' - - name: Build - run: GO111MODULE=on CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build . - - name: Upload build - uses: actions/upload-artifact@v2 - with: - name: micrach - path: micrach - retention-days: 1 + - name: Checkout + uses: actions/checkout@v6 - release: - needs: build + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: "1.26.5" + cache: true + cache-dependency-path: go.sum - runs-on: ubuntu-latest + - name: Verify formatting + run: | + files="$(gofmt -l .)" + test -z "$files" || { printf '%s\n' "$files"; exit 1; } - # TODO: Need to be removed after separate configs are made - if: github.ref == 'refs/heads/master' + - name: Test + run: go test ./... - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Download build - uses: actions/download-artifact@v2 - with: - name: micrach - path: micrach - - name: Build docker image and push to GitHub registry - uses: docker/build-push-action@v1 - with: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - registry: docker.pkg.github.com - repository: yanislav-igonin/micrach-go/micrach - dockerfile: Dockerfile - tags: latest - # build_args: CI_COMMIT_TAG=${{ github.event.release.tag_name }} + - name: Vet + run: go vet ./... + - name: Build + run: go build ./... - deploy: - needs: release + - name: Build Linux binary + run: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /tmp/micrach-linux-amd64 . - runs-on: ubuntu-latest + - name: Install govulncheck + run: go install golang.org/x/vuln/cmd/govulncheck@v1.6.0 - # TODO: Need to be removed after separate configs are made - if: github.ref == 'refs/heads/master' + - name: Scan reachable vulnerabilities + run: govulncheck ./... - steps: - - name: Deploy - uses: appleboy/ssh-action@master - env: - REGISTRY: ghcr.io/yanislav-igonin/micrach-go/micrach - SERVICE_NAME: micrach_app - with: - host: ${{ secrets.VPS_HOST }} - username: ${{ secrets.VPS_SSH_USERNAME }} - key: ${{ secrets.VPS_SSH_PRIVATE_KEY }} - script: | - docker login -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} ${{ env.REGISTRY }} - # docker pull ${{ env.REGISTRY }}:${{ github.event.release.tag_name }} - docker pull ${{ env.REGISTRY }}:latest - # docker service update --image ${{ env.REGISTRY }}:${{ github.event.release.tag_name }} ${{ env.SERVICE_NAME }} - docker service update --image ${{ env.REGISTRY }}:latest ${{ env.SERVICE_NAME }} \ No newline at end of file + - name: Build container image + run: docker build --tag micrach:ci . diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..ad73f9b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,169 @@ +# AGENTS.md + +Guidance for coding agents working in this repository. Keep changes focused, +preserve existing behavior unless the task explicitly changes it, and verify +all claims with fresh commands. + +## Project Overview + +micrach is a small, server-rendered, single-board imageboard. It uses Go, +Fiber v2, Fiber HTML templates, PostgreSQL through `pgx`, and filesystem-backed +image uploads. It intentionally uses little browser-side JavaScript. + +The service supports thread creation, replies, JPEG/PNG attachments, +thumbnails, CAPTCHA, rate limiting, pagination, bump limits, `sage`, thread +archival, and optional external gateway registration. + +## Repository Map + +- `main.go`: application startup, middleware, static mounts, routes, and server. +- `controllers/`: Fiber handlers, request parsing, HTTP responses, and form flow. +- `repositories/`: SQL queries, persistence types, and template view data. +- `db/`: PostgreSQL pool initialization and startup migration runner. +- `config/`: environment parsing and defaults. +- `gateway/`: optional gateway registration and authenticated ping endpoint. +- `utils/`: post validation, upload directories, and image thumbnail handling. +- `files/`: shared filesystem read helpers. +- `build/`: production CSS cache-busting rewrite. +- `templates/{pages,components,head}`: server-rendered HTML. +- `static/`: checked-in CSS, icons, and error images. +- `uploads/`: generated runtime files; ignored by Git and never commit them. +- `migrations/`: ordered PostgreSQL changes named `-.sql`. +- `tests/`: optional Vegeta load test, not the unit-test suite. + +## Runtime Flow and Routes + +Startup loads `.env`, initializes configuration and the DB pool, applies +missing migrations, optionally seeds data, performs production CSS rewriting, +creates `uploads/`, then starts Fiber. + +| Method | Path | Handler / purpose | +| --- | --- | --- | +| `GET` | `/` | Render paginated thread catalog. | +| `POST` | `/` | Validate and create a thread. | +| `GET` | `/:threadID` | Render one thread and its replies. | +| `POST` | `/:threadID` | Validate and add a reply. | +| `GET` | `/captcha/:captchaID` | Render CAPTCHA PNG. | +| `GET` | `/api/ping` | Gateway health endpoint; registered only when `GATEWAY_URL` is set. | + +Static content is served from `/static`; generated files are served from +`/uploads`. Thread and reply creation spans PostgreSQL plus filesystem writes, +so review both paths when changing upload behavior. + +## Local Setup and Commands + +Use Go 1.26. From the repository root, copy the development configuration, +start the PostgreSQL 18 service, and run micrach on the host: + +```sh +cp .env.example .env +docker compose up -d db +go run . +``` + +The example `POSTGRES_URL` matches the Compose service. `make dev` provides +automatic reload and requires `nodemon`. + +Common checks: + +```sh +go fmt ./... +go test ./... +go vet ./... +go build . +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /tmp/micrach-linux-amd64 . +``` + +`sh tests/load.sh` requires Vegeta and targets a historical public URL by +default; change the target to a controlled environment before use. Do not run +the load test as routine verification. + +## Configuration and Generated State + +- `.env` is local and must never be committed. +- `POSTGRES_URL` selects the database; startup applies migrations automatically. +- `IS_DB_SEEDED=true` adds sample data on every startup, not only once. +- `ENV=production` renames CSS files and rewrites templates in place at startup. + Do not use this mode in a development checkout unless that mutation is wanted. +- `GATEWAY_URL=""` disables gateway routes and registration. +- Uploads are limited to four JPEG/PNG files, 3 MiB each. Titles allow 100 + runes; post text allows 1,000 runes. +- Runtime uploads, built binaries, `.env`, and load-test artifacts are ignored + and must remain untracked. + +## Architecture and Coding Conventions + +- Keep handlers thin: HTTP parsing/status/rendering in `controllers/`; SQL and + persistence behavior in `repositories/`. +- Keep DB startup and migration mechanics in `db/`, environment parsing in + `config/`, gateway behavior in `gateway/`, and reusable helpers in `utils/` + or `files/`. +- Follow existing package naming: short, lowercase names. Use `PascalCase` for + exported identifiers and `camelCase` for unexported identifiers. +- Format Go with `gofmt`/`go fmt`; do not hand-format with spaces. +- Follow existing kebab-case naming for templates, CSS, and migration + descriptions. Preserve current server-rendered approach and minimal-JS goal. +- Use parameterized SQL. Keep related DB operations transactional where the + existing workflow requires atomicity. +- Avoid unrelated refactors. If a task exposes adjacent problems, report them + separately unless they block the requested work. + +## Change Guide + +- Route or request behavior: update `main.go` and/or `controllers/`, then cover + status codes, redirects, validation errors, and template data. +- Persistence behavior: update `repositories/`; add a migration for schema + changes rather than editing an already-applied migration. +- Schema changes: add the next numeric file in `migrations/`. Keep filename + shape exactly `-.sql`; the runner parses it directly. +- Page or component changes: edit `templates/` and matching `static/styles/`. + Check catalog, thread, error, empty, and pagination states as relevant. +- Upload changes: inspect controller transaction flow, `utils/`, repository file + records, original/thumbnail paths, validation limits, and cleanup behavior. +- Config changes: update `config/config.go`, `.env.example`, and the README + config table together. +- Deployment changes: + - `Dockerfile` builds the application in a pinned Go builder stage and runs it + as a non-root user in a small runtime image. + - `docker-compose.yml` starts local PostgreSQL only; run micrach on the host. + - CI verifies the project and container build but does not publish or deploy. + +## Testing Expectations + +Go unit tests are checked in across the application packages; there is no +coverage threshold. Add `*_test.go` files beside changed code, use `TestXxx`, +and prefer table-driven cases when practical. Repository tests must use +disposable test data or a dedicated database, never a shared production +database. + +Minimum verification for code changes: + +1. Run `go fmt ./...` and review the resulting diff. +2. Run `go test ./...`. +3. Run `go vet ./...`. +4. Run `go build .` (or the Linux CI build for deployment-sensitive work). +5. For templates/CSS, start the app and manually inspect affected responsive + states; include screenshots in the PR. + +For documentation-only changes, inspect the rendered Markdown, verify all local +links and commands against the repository, and review `git diff --check`. + +## Git, Commits, and Pull Requests + +- Inspect `git status` before editing. Preserve user changes and unrelated files. +- Never commit `.env`, credentials, uploads, binaries, or generated load-test + outputs. +- Prefer focused Conventional Commit subjects, such as + `fix: reject oversized uploads` or `docs: expand setup guide`. +- Keep each commit buildable. Do not rewrite history or discard local changes + unless explicitly requested. +- PRs should describe behavior, config/migration impact, verification commands, + linked issues, and screenshots for UI changes. + +## Agent Completion Checklist + +- Requested scope is fully addressed; unrelated behavior is untouched. +- New behavior has tests where practical. +- Documentation and `.env.example` match config/code changes. +- No secret, upload, binary, or generated artifact is included. +- Fresh verification commands pass, and final report names commands actually run. diff --git a/Dockerfile b/Dockerfile index df9618d..5a256e4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,24 @@ -FROM busybox +FROM golang:1.26.5-alpine3.24 AS build + +WORKDIR /src +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/micrach . + +FROM alpine:3.24.1 + +RUN apk add --no-cache ca-certificates \ + && addgroup -S micrach \ + && adduser -S -G micrach micrach \ + && mkdir -p /app/uploads \ + && chown -R micrach:micrach /app + WORKDIR /app -COPY templates/ templates/ -COPY static/ static/ -COPY migrations/ migrations/ -COPY micrach ./ -RUN chmod +x /app/micrach -ENTRYPOINT ["/app/micrach"] \ No newline at end of file +COPY --from=build /out/micrach ./micrach +COPY --chown=micrach:micrach templates/ templates/ +COPY --chown=micrach:micrach static/ static/ +COPY --chown=micrach:micrach migrations/ migrations/ + +USER micrach +ENTRYPOINT ["./micrach"] diff --git a/Makefile b/Makefile index 913d0b1..f5757a7 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,13 @@ +.PHONY: run dev infra-up infra-down + +run: + go run . + dev: - nodemon --exec go run main.go --signal SIGTERM + nodemon --exec go run . --signal SIGTERM + +infra-up: + docker compose up -d db + +infra-down: + docker compose down diff --git a/README.md b/README.md index c2abb54..6c4c766 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,142 @@ # micrach -Go tryout. Single board imageboard. -UPD: stopped the service from the link above, too much shit posted from TOR, don't have time to moderate :( +micrach is a small, server-rendered imageboard written in Go. It provides one +board where visitors can create threads, reply with text or images, bump or +`sage` threads, and browse the thread catalog without a client-side SPA. -## Motivation -Tired of a fucking SPAs, so I decided to write this simple and lightweight imageboard (microboard, actually) with as less JS as possible. -Also, I wanted to learn Go, so I decided to write this in Go. +The project began as a Go learning project and as an experiment in building a +lightweight imageboard with very little JavaScript. The former public instance +is offline because it required more moderation than the maintainer could +provide; the source remains available for local use and development. -Maybe I'll add an api and create a SPA with the Next.js framework in the future, but right now I'm trying to make this as simple as possible. +## Features -## Prerequisites -1. Go 1.13+. -2. PostgreSQL. -3. Create `.env` file from `.env.example`, change env vars to your needs. -4. Create db with the name that is specified in `.env` file in `POSTGRES_URL` env var. +- Server-rendered HTML using Fiber templates +- Thread creation, replies, catalog pagination, bump limits, and `sage` +- JPEG and PNG uploads with generated thumbnails +- Optional CAPTCHA and request rate limiting +- Automatic PostgreSQL migrations on startup +- Automatic archival when the active-thread limit is reached +- Optional registration with an external board gateway + +## Stack + +- Go 1.26 +- Fiber v2 +- PostgreSQL 18 for local development +- PostgreSQL access through [`pgx`](https://github.com/jackc/pgx) +- Plain HTML templates and CSS + +## Run locally + +### Prerequisites + +- Go 1.26 +- Docker with Docker Compose +- `nodemon` when using automatic reload + +### Setup + +From the repository root: -## Run -Just run: ```sh -go run main.go +cp .env.example .env +docker compose up -d db +go run . ``` -**In development** I prefer to run it with [nodemon](https://github.com/remy/nodemon) for live reload. +The example configuration matches the PostgreSQL 18 service started by +Compose. Compose runs PostgreSQL only; the micrach application runs on the +host. Edit `.env` when you need different local settings. For normal +development, keep `IS_DB_SEEDED=false`; when true, sample rows are inserted +every time the application starts. -## How it looks -![SCR-20230509-jreo](https://user-images.githubusercontent.com/15846431/237027105-481f2d55-541b-4c3b-8dd0-db464c25102f.jpeg) +For automatic reload, install `nodemon`, start PostgreSQL as shown above, and +run: + +```sh +make dev +``` + +Open [http://localhost:3000](http://localhost:3000), or the port configured by +`PORT`. + +Startup connects to PostgreSQL, applies missing files from `migrations/`, and +creates the ignored local `uploads/` directory. Uploaded originals and +thumbnails are stored on disk, so keep that directory persistent when running +the service in a container. + +## Configuration + +`.env` is loaded automatically and is ignored by Git. + +| Variable | Default in code | Purpose | +| --- | --- | --- | +| `ENV` | `release` | Runtime mode. `production` enables CSS filename rewriting at startup. | +| `PORT` | `3000` | HTTP listen port. | +| `POSTGRES_URL` | `postgresql://localhost/micrach` | PostgreSQL connection URL. | +| `IS_DB_SEEDED` | `false` | Insert sample posts at startup. Avoid enabling repeatedly. | +| `IS_RATE_LIMITER_ENABLED` | `true` | Enable Fiber request limiting. | +| `THREADS_MAX_COUNT` | `50` | Maximum active threads before old threads are archived. | +| `THREAD_BUMP_LIMIT` | `500` | Reply count after which a thread no longer bumps. | +| `IS_CAPTCHA_ACTIVE` | `true` | Require CAPTCHA for new threads and replies. | +| `GATEWAY_URL` | empty | External gateway base URL. Empty disables gateway integration. | +| `GATEWAY_API_KEY` | empty | Shared key used for gateway registration and `/api/ping`. | +| `GATEWAY_BOARD_ID` | empty | Board identifier sent to the gateway. | +| `GATEWAY_BOARD_URL` | empty | Parsed from the environment but not currently used by the gateway client. | +| `GATEWAY_BOARD_DESCRIPTION` | empty | Board description sent to the gateway. | + +See [.env.example](.env.example) for a complete example. + +## Development commands + +```sh +# Format, test, vet, and build. +go fmt ./... +go test ./... +go vet ./... +go build . + +# Reproduce the Linux CI build. +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /tmp/micrach-linux-amd64 . +``` + +`tests/load.sh` is an optional Vegeta load test. Its checked-in target is a +historical public URL, so change it to an environment you own before running +the script. Generated load-test files are ignored by Git. + +## Containers and CI + +The multi-stage `Dockerfile` builds micrach in a pinned Go builder image and +runs it as a non-root user in a small runtime image. Verify the image locally: + +```sh +docker build -t micrach:local . +``` + +CI checks formatting, tests, vet, native and Linux builds, reachable +vulnerabilities, and Docker image construction. It has read-only repository +permissions and does not publish or deploy images. + +## Project layout + +```text +controllers/ HTTP handlers and request validation flow +repositories/ PostgreSQL queries and view data types +db/ connection pool and migration runner +config/ environment parsing +gateway/ optional external gateway integration +templates/ server-rendered pages, components, and head fragments +static/ CSS, icons, and error images +uploads/ runtime uploads (created locally, ignored by Git) +migrations/ ordered PostgreSQL schema changes +tests/ optional load-test scripts +``` + +## Screenshot + +![micrach catalog](https://user-images.githubusercontent.com/15846431/237027105-481f2d55-541b-4c3b-8dd0-db464c25102f.jpeg) + +## License + +[MIT](LICENSE) diff --git a/build/css.go b/build/css.go index ca31c2c..c58a581 100644 --- a/build/css.go +++ b/build/css.go @@ -1,7 +1,6 @@ package build import ( - "io/ioutil" "math/rand" "os" "strings" @@ -10,7 +9,7 @@ import ( // Gets file paths from directory recursively. func getFilePathsRecursively(dir string) ([]string, error) { var paths []string - files, err := ioutil.ReadDir(dir) + files, err := os.ReadDir(dir) if err != nil { return nil, err } @@ -32,7 +31,7 @@ func getFilePathsRecursively(dir string) ([]string, error) { // Returns all css files paths in ../static/styles folder. func getCssFilesPaths() []string { var paths []string - files, err := ioutil.ReadDir("static/styles") + files, err := os.ReadDir("static/styles") if err != nil { panic(err) } @@ -99,7 +98,7 @@ func RenameCss() { } for _, htmlTemplatePath := range htmlTemplatePaths { - htmlTemplate, err := ioutil.ReadFile(htmlTemplatePath) + htmlTemplate, err := os.ReadFile(htmlTemplatePath) if err != nil { panic(err) } @@ -108,7 +107,7 @@ func RenameCss() { htmlTemplate = []byte(strings.Replace(string(htmlTemplate), origPath, newPath, -1)) } // write html template to file - err = ioutil.WriteFile(htmlTemplatePath, htmlTemplate, 0644) + err = os.WriteFile(htmlTemplatePath, htmlTemplate, 0644) if err != nil { panic(err) } diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 0000000..ad13382 --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,67 @@ +package config + +import "testing" + +func TestGetValueOrDefaultBoolean(t *testing.T) { + tests := []struct { + name string + value string + defaultValue bool + want bool + }{ + {name: "default true", defaultValue: true, want: true}, + {name: "default false", defaultValue: false, want: false}, + {name: "explicit true", value: "true", want: true}, + {name: "explicit false", value: "false", defaultValue: true, want: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getValueOrDefaultBoolean(tt.value, tt.defaultValue); got != tt.want { + t.Fatalf("getValueOrDefaultBoolean() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetValueOrDefaultInt(t *testing.T) { + tests := []struct { + name string + value string + defaultValue int + want int + }{ + {name: "default", defaultValue: 3000, want: 3000}, + {name: "configured", value: "8080", defaultValue: 3000, want: 8080}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getValueOrDefaultInt(tt.value, tt.defaultValue); got != tt.want { + t.Fatalf("getValueOrDefaultInt() = %d, want %d", got, tt.want) + } + }) + } +} + +func TestDefaultConfigs(t *testing.T) { + for _, key := range []string{ + "ENV", "PORT", "IS_DB_SEEDED", "IS_RATE_LIMITER_ENABLED", + "THREADS_MAX_COUNT", "THREAD_BUMP_LIMIT", "IS_CAPTCHA_ACTIVE", + "GATEWAY_URL", "GATEWAY_API_KEY", "GATEWAY_BOARD_ID", + "GATEWAY_BOARD_URL", "GATEWAY_BOARD_DESCRIPTION", "POSTGRES_URL", + } { + t.Setenv(key, "") + } + + app := getAppConfig() + if app.Env != "release" || app.Port != 3000 || app.IsDbSeeded { + t.Fatalf("unexpected app defaults: %+v", app) + } + if !app.IsRateLimiterEnabled || app.ThreadsMaxCount != 50 || app.ThreadBumpLimit != 500 || !app.IsCaptchaActive { + t.Fatalf("unexpected feature defaults: %+v", app) + } + if db := getDbConfig(); db.Url != "postgresql://localhost/micrach" { + t.Fatalf("unexpected database default: %+v", db) + } +} diff --git a/controllers/threads_controller.go b/controllers/threads_controller.go index 8320e3b..8e74b7c 100644 --- a/controllers/threads_controller.go +++ b/controllers/threads_controller.go @@ -1,7 +1,7 @@ package controllers import ( - "context" + "errors" "log" "math" "path/filepath" @@ -10,6 +10,7 @@ import ( "github.com/dchest/captcha" "github.com/gofiber/fiber/v2" + "github.com/jackc/pgx/v5" "micrach/config" "micrach/db" @@ -18,6 +19,7 @@ import ( ) func GetThreads(c *fiber.Ctx) error { + ctx := c.UserContext() pageString := c.Query("page", "1") page, err := strconv.Atoi(pageString) if err != nil { @@ -30,12 +32,12 @@ func GetThreads(c *fiber.Ctx) error { limit := 10 offset := limit * (page - 1) - threads, err := repositories.Posts.Get(limit, offset) + threads, err := repositories.Posts.Get(ctx, limit, offset) if err != nil { log.Println("error:", err) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) } - count, err := repositories.Posts.GetThreadsCount() + count, err := repositories.Posts.GetThreadsCount(ctx) if err != nil { log.Println("error:", err) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) @@ -62,11 +64,12 @@ func GetThreads(c *fiber.Ctx) error { } func GetThread(c *fiber.Ctx) error { + ctx := c.UserContext() threadID, err := c.ParamsInt("threadID") if err != nil { return c.Status(fiber.StatusNotFound).Render("pages/404", nil) } - thread, err := repositories.Posts.GetThreadByPostID(threadID) + thread, err := repositories.Posts.GetThreadByPostID(ctx, threadID) if err != nil { log.Println("error:", err) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) @@ -89,6 +92,7 @@ func GetThread(c *fiber.Ctx) error { } func CreateThread(c *fiber.Ctx) error { + ctx := c.UserContext() form, err := c.MultipartForm() if err != nil { log.Println("error:", err) @@ -119,38 +123,43 @@ func CreateThread(c *fiber.Ctx) error { } } - conn, err := db.Pool.Acquire(context.TODO()) + conn, err := db.Pool.Acquire(ctx) if err != nil { log.Println("error:", err) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) } defer conn.Release() - threadsCount, err := repositories.Posts.GetThreadsCount() + threadsCount, err := repositories.Posts.GetThreadsCount(ctx) if err != nil { log.Println("error:", err) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) } if threadsCount >= config.App.ThreadsMaxCount { - oldestThreadUpdatedAt, err := repositories.Posts.GetOldestThreadUpdatedAt() + oldestThreadUpdatedAt, err := repositories.Posts.GetOldestThreadUpdatedAt(ctx) if err != nil { log.Println("error:", err) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) } - err = repositories.Posts.ArchiveThreadsFrom(oldestThreadUpdatedAt) + err = repositories.Posts.ArchiveThreadsFrom(ctx, oldestThreadUpdatedAt) if err != nil { log.Println("error:", err) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) } } - tx, err := conn.Begin(context.TODO()) + tx, err := conn.Begin(ctx) if err != nil { log.Println("error:", err) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) } - defer tx.Rollback(context.TODO()) + defer func() { + rollbackErr := tx.Rollback(ctx) + if rollbackErr != nil && !errors.Is(rollbackErr, pgx.ErrTxClosed) { + log.Println("error:", rollbackErr) + } + }() post := repositories.Post{ IsParent: true, @@ -158,7 +167,7 @@ func CreateThread(c *fiber.Ctx) error { Text: text, IsSage: false, } - threadID, err := repositories.Posts.CreateInTx(tx, post) + threadID, err := repositories.Posts.CreateInTx(ctx, tx, post) if err != nil { log.Println("error:", err) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) @@ -179,7 +188,7 @@ func CreateThread(c *fiber.Ctx) error { Size: int(fileInRequest.Size), } - fileID, err := repositories.Files.CreateInTx(tx, file) + fileID, err := repositories.Files.CreateInTx(ctx, tx, file) if err != nil { log.Println("error:", err) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) @@ -210,7 +219,10 @@ func CreateThread(c *fiber.Ctx) error { } } - tx.Commit(context.TODO()) + if err := tx.Commit(ctx); err != nil { + log.Println("error:", err) + return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) + } path := "/" + strconv.Itoa(threadID) return c.Redirect(path, fiber.StatusFound) @@ -218,12 +230,13 @@ func CreateThread(c *fiber.Ctx) error { // Add new post in thread func UpdateThread(c *fiber.Ctx) error { + ctx := c.UserContext() threadID, err := c.ParamsInt("threadID") if err != nil { return c.Status(fiber.StatusNotFound).Render("pages/404", nil) } - isArchived, err := repositories.Posts.GetIfThreadIsArchived(threadID) + isArchived, err := repositories.Posts.GetIfThreadIsArchived(ctx, threadID) if isArchived { errorHtmlData := repositories.BadRequestHtmlData{ Message: repositories.ThreadIsArchivedErrorMessage, @@ -271,7 +284,7 @@ func UpdateThread(c *fiber.Ctx) error { } isSage := isSageString == "on" - conn, err := db.Pool.Acquire(context.TODO()) + conn, err := db.Pool.Acquire(ctx) if err != nil { log.Println("error:", err) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) @@ -279,13 +292,18 @@ func UpdateThread(c *fiber.Ctx) error { } defer conn.Release() - tx, err := conn.Begin(context.TODO()) + tx, err := conn.Begin(ctx) if err != nil { log.Println("error:", err) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) } - defer tx.Rollback(context.TODO()) + defer func() { + rollbackErr := tx.Rollback(ctx) + if rollbackErr != nil && !errors.Is(rollbackErr, pgx.ErrTxClosed) { + log.Println("error:", rollbackErr) + } + }() post := repositories.Post{ IsParent: false, @@ -294,14 +312,14 @@ func UpdateThread(c *fiber.Ctx) error { Text: text, IsSage: isSage, } - postID, err := repositories.Posts.CreateInTx(tx, post) + postID, err := repositories.Posts.CreateInTx(ctx, tx, post) if err != nil { log.Println("error:", err) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) } - postsCountInThread, err := repositories.Posts.GetThreadPostsCount(threadID) + postsCountInThread, err := repositories.Posts.GetThreadPostsCount(ctx, threadID) if err != nil { log.Println("error:", err) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) @@ -310,7 +328,7 @@ func UpdateThread(c *fiber.Ctx) error { isBumpLimit := postsCountInThread >= config.App.ThreadBumpLimit isThreadBumped := !isBumpLimit && !isSage && !post.IsParent if isThreadBumped { - err = repositories.Posts.BumpThreadInTx(tx, threadID) + err = repositories.Posts.BumpThreadInTx(ctx, tx, threadID) if err != nil { log.Println("error:", err) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) @@ -327,7 +345,7 @@ func UpdateThread(c *fiber.Ctx) error { Size: int(fileInRequest.Size), } - fileID, err := repositories.Files.CreateInTx(tx, file) + fileID, err := repositories.Files.CreateInTx(ctx, tx, file) if err != nil { log.Println("error:", err) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) @@ -362,7 +380,10 @@ func UpdateThread(c *fiber.Ctx) error { } } - tx.Commit(context.TODO()) + if err := tx.Commit(ctx); err != nil { + log.Println("error:", err) + return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) + } path := "/" + strconv.Itoa(threadID) + "#" + strconv.Itoa(postID) return c.Redirect(path) diff --git a/controllers/threads_controller_test.go b/controllers/threads_controller_test.go new file mode 100644 index 0000000..2e6cff3 --- /dev/null +++ b/controllers/threads_controller_test.go @@ -0,0 +1,46 @@ +package controllers + +import ( + "io" + "net/http/httptest" + "testing" + + "github.com/gofiber/fiber/v2" +) + +type testViews struct{} + +func (testViews) Load() error { return nil } + +func (testViews) Render(out io.Writer, name string, _ interface{}, _ ...string) error { + _, err := io.WriteString(out, name) + return err +} + +func TestGetThreadsRejectsInvalidPage(t *testing.T) { + app := fiber.New(fiber.Config{Views: testViews{}}) + app.Get("/", GetThreads) + + resp, err := app.Test(httptest.NewRequest("GET", "/?page=0", nil)) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + if resp.StatusCode != fiber.StatusNotFound { + t.Fatalf("status = %d, want %d", resp.StatusCode, fiber.StatusNotFound) + } +} + +func TestGetThreadRejectsInvalidID(t *testing.T) { + app := fiber.New(fiber.Config{Views: testViews{}}) + app.Get("/:threadID", GetThread) + + resp, err := app.Test(httptest.NewRequest("GET", "/invalid", nil)) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + if resp.StatusCode != fiber.StatusNotFound { + t.Fatalf("status = %d, want %d", resp.StatusCode, fiber.StatusNotFound) + } +} diff --git a/db/db.go b/db/db.go index fc0ad90..fbe0d53 100644 --- a/db/db.go +++ b/db/db.go @@ -2,6 +2,7 @@ package db import ( "context" + "errors" "log" "path/filepath" "strconv" @@ -10,7 +11,8 @@ import ( Config "micrach/config" Files "micrach/files" - "github.com/jackc/pgx/v4/pgxpool" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgxpool" ) var Pool *pgxpool.Pool @@ -21,9 +23,9 @@ type Migration struct { Name string } -func Init() { +func Init(ctx context.Context) { var err error - Pool, err = pgxpool.Connect(context.TODO(), Config.Db.Url) + Pool, err = pgxpool.New(ctx, Config.Db.Url) if err != nil { log.Println("database - offline") log.Panicln(err) @@ -32,8 +34,8 @@ func Init() { log.Println("database - online") } -func Migrate() { - dbMigrations := getDbMigrations() +func Migrate(ctx context.Context) { + dbMigrations := getDbMigrations(ctx) sqlMigrations := Files.GetFullFilePathsInFolder("migrations") for _, m := range sqlMigrations { filename := filepath.Base(m) @@ -48,7 +50,7 @@ func Migrate() { _, isMigrationInDb := dbMigrations[id] if !isMigrationInDb { sql := Files.ReadFileText(m) - runMigration(id, name, sql) + runMigration(ctx, id, name, sql) log.Println("migration - " + name + " - online") } } @@ -56,29 +58,27 @@ func Migrate() { log.Println("migrations - online") } -func runMigration(mid int, mname, msql string) { - _, err := Pool.Exec(context.TODO(), msql) +func runMigration(ctx context.Context, mid int, mname, msql string) { + _, err := Pool.Exec(ctx, msql) if err != nil { log.Panicln(err) } - sql := `INSERT INTO migrations (id, name) VALUES ($1, $2)` - _, err = Pool.Query(context.TODO(), sql, mid, mname) + _, err = Pool.Exec(ctx, `INSERT INTO migrations (id, name) VALUES ($1, $2)`, mid, mname) if err != nil { log.Panicln(err) } } -func getDbMigrations() MigrationsMap { - sql := `SELECT id, name FROM migrations` - rows, err := Pool.Query(context.TODO(), sql) - if err != nil && isNotNonExistentMigrationsTable(err) { +func getDbMigrations(ctx context.Context) MigrationsMap { + rows, err := Pool.Query(ctx, `SELECT id, name FROM migrations`) + if err != nil { + if isUndefinedTable(err) { + return make(MigrationsMap) + } log.Panicln(err) } - - if rows.Err() != nil && isNotNonExistentMigrationsTable(rows.Err()) { - log.Panicln(rows.Err()) - } + defer rows.Close() migrationsMap := make(MigrationsMap) for rows.Next() { @@ -90,10 +90,14 @@ func getDbMigrations() MigrationsMap { migrationsMap[m.ID] = m.Name } + if err := rows.Err(); err != nil { + log.Panicln(err) + } return migrationsMap } -func isNotNonExistentMigrationsTable(err error) bool { - return err.Error() != `ERROR: relation "migrations" does not exist (SQLSTATE 42P01)` +func isUndefinedTable(err error) bool { + var pgErr *pgconn.PgError + return errors.As(err, &pgErr) && pgErr.Code == "42P01" } diff --git a/db/db_test.go b/db/db_test.go new file mode 100644 index 0000000..1d1382f --- /dev/null +++ b/db/db_test.go @@ -0,0 +1,28 @@ +package db + +import ( + "errors" + "testing" + + "github.com/jackc/pgx/v5/pgconn" +) + +func TestIsUndefinedTable(t *testing.T) { + tests := []struct { + name string + err error + want bool + }{ + {name: "undefined table", err: &pgconn.PgError{Code: "42P01"}, want: true}, + {name: "other postgres error", err: &pgconn.PgError{Code: "23505"}}, + {name: "ordinary error", err: errors.New("boom")}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isUndefinedTable(tt.err); got != tt.want { + t.Fatalf("isUndefinedTable() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/docker-compose.yml b/docker-compose.yml index 644c933..838b4e0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,58 +1,20 @@ -version: "3.7" - services: - app: - image: ghcr.io/yanislav-igonin/micrach-go/micrach:latest - networks: - - web - environment: - ENV: release - PORT: ${PORT} - IS_DB_SEEDED: ${IS_DB_SEEDED} - IS_RATE_LIMITER_ENABLED: ${IS_RATE_LIMITER_ENABLED} - THREADS_MAX_COUNT: ${THREADS_MAX_COUNT} - POSTGRES_URL: ${POSTGRES_URL} - THREAD_BUMP_LIMIT: ${THREAD_BUMP_LIMIT} - IS_CAPTCHA_ACTIVE: ${IS_CAPTCHA_ACTIVE} - GATEWAY_URL: ${GATEWAY_URL} - GATEWAY_API_KEY: ${GATEWAY_API_KEY} - GATEWAY_BOARD_ID: ${GATEWAY_BOARD_ID} - GATEWAY_BOARD_URL: ${GATEWAY_BOARD_URL} - GATEWAY_BOARD_DESCRIPTION: ${GATEWAY_BOARD_DESCRIPTION} + db: + image: postgres:18.4-alpine3.23 + environment: + POSTGRES_DB: micrach + POSTGRES_USER: micrach + POSTGRES_PASSWORD: micrach + ports: + - "5432:5432" volumes: - - /root/micrach-go/uploads:/app/uploads - deploy: - mode: global - placement: - constraints: - - node.role == manager - update_config: - parallelism: 1 - delay: 10s - restart_policy: - condition: on-failure - labels: - traefik.enable: "true" - traefik.docker.network: "web" - - traefik.http.services.micrach.loadbalancer.server.port: ${PORT} + - micrach-postgres:/var/lib/postgresql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U micrach -d micrach"] + interval: 2s + timeout: 5s + retries: 15 + start_period: 5s - traefik.http.middlewares.micrach-https-redirect.redirectscheme.scheme: "https" - traefik.http.middlewares.prefixstripper.stripprefix.prefixes: "/${GATEWAY_BOARD_ID}" - traefik.http.middlewares.prefixstripper.stripprefix.forceSlash: "false" - - traefik.http.routers.micrach.entrypoints: "http" - traefik.http.routers.micrach.rule: "Host(`micrach.igonin.dev`) && PathPrefix(`/${GATEWAY_BOARD_ID}`)" - traefik.http.routers.micrach.middlewares: "micrach-https-redirect" - traefik.http.routers.micrach.middlewares: "prefixstripper" - - traefik.http.routers.micrach-secure.entrypoints: "https" - traefik.http.routers.micrach-secure.rule: "Host(`micrach.igonin.dev`) && PathPrefix(`/${GATEWAY_BOARD_ID}`)" - traefik.http.routers.micrach-secure.tls: "true" - traefik.http.routers.micrach-secure.service: "micrach" - traefik.http.routers.micrach-secure.middlewares: "prefixstripper" - -networks: - web: - driver: overlay - external: true +volumes: + micrach-postgres: diff --git a/docs/superpowers/plans/2026-07-15-wave-1-modernization.md b/docs/superpowers/plans/2026-07-15-wave-1-modernization.md new file mode 100644 index 0000000..bd7399e --- /dev/null +++ b/docs/superpowers/plans/2026-07-15-wave-1-modernization.md @@ -0,0 +1,1306 @@ +# Wave 1 Modernization Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Establish a supported Go 1.26 baseline with current Fiber v2 and pgx v5 dependencies, local PostgreSQL infrastructure, build-only CI, a reproducible container image, and focused regression tests without changing application behavior or architecture. + +**Architecture:** Preserve the existing Fiber controllers, global configuration and repository objects, PostgreSQL schema, server-rendered templates, and filesystem upload layout. Apply compatibility changes in reviewable slices: characterize behavior first, update the HTTP/toolchain dependencies, migrate pgx with explicit context propagation, modernize deprecated APIs, then replace development and CI infrastructure. Fiber v3 remains a separate Wave 2 implementation after this plan is fully green. + +**Tech Stack:** Go 1.26.5, Fiber v2.52.14, Fiber HTML template v2.1.3, pgx v5.10.0, PostgreSQL 18.4, Docker Compose, GitHub Actions, Dependabot, and `govulncheck` v1.6.0. + +## Global Constraints + +- Preserve all routes, templates, HTTP statuses, database schema, upload paths, and minimal-JavaScript behavior. +- Preserve existing package boundaries; do not redesign handlers or repositories. +- Do not add database migrations, Redis, a proxy, a queue, or another service. +- Run the application on the host with `go run .` or `make dev`; Compose runs PostgreSQL only. +- Keep Fiber on v2 throughout Wave 1. +- Do not publish container images or deploy from CI. +- Never use a production or shared PostgreSQL database for tests. +- Do not commit `.env`, uploads, binaries, credentials, or generated test artifacts. + +## File Map + +**Create:** + +- `config/config_test.go`: configuration default and parsing characterization. +- `utils/utils_test.go`: post and upload validation boundaries. +- `controllers/threads_controller_test.go`: DB-free HTTP error-path smoke tests. +- `repositories/files_repository_test.go`: pgx v5 integer-array conversion. +- `db/db_test.go`: PostgreSQL undefined-table error classification. +- `.dockerignore`: exclude local and generated state from image context. +- `.github/dependabot.yml`: reviewed Go module and Actions updates. + +**Modify:** + +- `go.mod`, `go.sum`: Go line and dependency graph. +- `main.go`: Fiber template v2 import, startup context, and contextual startup calls. +- `controllers/threads_controller.go`: request context propagation and checked transaction completion. +- `repositories/posts_repository.go`: pgx v5 imports, contextual methods, and rows lifecycle. +- `repositories/files_repository.go`: pgx v5 imports, contextual methods, and native integer-array encoding. +- `repositories/seeds.go`: explicit startup context. +- `db/db.go`: pgx v5 pool, startup context, rows lifecycle, and SQLSTATE handling. +- `build/css.go`, `files/get_files_in_folder.go`, `gateway/connect.go`: replace deprecated `ioutil`; close gateway response bodies. +- `Dockerfile`: multi-stage non-root application image. +- `docker-compose.yml`: PostgreSQL-only local infrastructure. +- `.env.example`: safe local defaults matching Compose. +- `Makefile`: host application and infrastructure commands. +- `.github/workflows/push.yml`: read-only verification CI. +- `README.md`, `AGENTS.md`: supported versions and workflows. + +**Delete:** + +- `.github/workflows/codesee-arch-diagram.yml`: obsolete CodeSee integration. + +--- + +### Task 1: Add Behavior Characterization Tests + +**Files:** + +- Create: `config/config_test.go` +- Create: `utils/utils_test.go` +- Create: `controllers/threads_controller_test.go` + +**Interfaces:** + +- Consumes: existing unexported configuration helpers, `utils.ValidatePost`, `controllers.GetThreads`, and `controllers.GetThread`. +- Produces: regression tests that must pass before and after dependency migration. + +- [ ] **Step 1: Record the pre-change baseline** + +Run: + +```bash +go test ./... +go vet ./... +go build ./... +``` + +Expected: all commands exit 0; packages report no test files. + +- [ ] **Step 2: Add configuration characterization tests** + +Create `config/config_test.go`: + +```go +package config + +import "testing" + +func TestGetValueOrDefaultBoolean(t *testing.T) { + tests := []struct { + name string + value string + defaultValue bool + want bool + }{ + {name: "default true", defaultValue: true, want: true}, + {name: "default false", defaultValue: false, want: false}, + {name: "explicit true", value: "true", want: true}, + {name: "explicit false", value: "false", defaultValue: true, want: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getValueOrDefaultBoolean(tt.value, tt.defaultValue); got != tt.want { + t.Fatalf("getValueOrDefaultBoolean() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetValueOrDefaultInt(t *testing.T) { + tests := []struct { + name string + value string + defaultValue int + want int + }{ + {name: "default", defaultValue: 3000, want: 3000}, + {name: "configured", value: "8080", defaultValue: 3000, want: 8080}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getValueOrDefaultInt(tt.value, tt.defaultValue); got != tt.want { + t.Fatalf("getValueOrDefaultInt() = %d, want %d", got, tt.want) + } + }) + } +} + +func TestDefaultConfigs(t *testing.T) { + for _, key := range []string{ + "ENV", "PORT", "IS_DB_SEEDED", "IS_RATE_LIMITER_ENABLED", + "THREADS_MAX_COUNT", "THREAD_BUMP_LIMIT", "IS_CAPTCHA_ACTIVE", + "GATEWAY_URL", "GATEWAY_API_KEY", "GATEWAY_BOARD_ID", + "GATEWAY_BOARD_URL", "GATEWAY_BOARD_DESCRIPTION", "POSTGRES_URL", + } { + t.Setenv(key, "") + } + + app := getAppConfig() + if app.Env != "release" || app.Port != 3000 || app.IsDbSeeded { + t.Fatalf("unexpected app defaults: %+v", app) + } + if !app.IsRateLimiterEnabled || app.ThreadsMaxCount != 50 || app.ThreadBumpLimit != 500 || !app.IsCaptchaActive { + t.Fatalf("unexpected feature defaults: %+v", app) + } + if db := getDbConfig(); db.Url != "postgresql://localhost/micrach" { + t.Fatalf("unexpected database default: %+v", db) + } +} +``` + +- [ ] **Step 3: Add post and upload validation tests** + +Create `utils/utils_test.go`: + +```go +package utils + +import ( + "mime/multipart" + "net/textproto" + "strings" + "testing" + + "micrach/repositories" +) + +func fileHeader(contentType string, size int64) *multipart.FileHeader { + header := make(textproto.MIMEHeader) + header.Set("Content-Type", contentType) + return &multipart.FileHeader{Filename: "upload", Header: header, Size: size} +} + +func TestValidatePost(t *testing.T) { + validPNG := fileHeader("image/png", 1) + validJPEG := fileHeader("image/jpeg", FILE_SIZE_IN_BYTES) + + tests := []struct { + name string + title string + text string + files []*multipart.FileHeader + want string + }{ + {name: "text only", text: "hello"}, + {name: "file only", files: []*multipart.FileHeader{validPNG}}, + {name: "empty", want: repositories.InvalidTextOrFilesErrorMessage}, + {name: "title over limit", title: strings.Repeat("я", 101), text: "ok", want: repositories.InvalidTitleLengthErrorMessage}, + {name: "text over limit", text: strings.Repeat("я", 1001), want: repositories.InvalidTextLengthErrorMessage}, + {name: "four files", files: []*multipart.FileHeader{validPNG, validPNG, validJPEG, validJPEG}}, + {name: "five files", files: []*multipart.FileHeader{validPNG, validPNG, validPNG, validPNG, validPNG}, want: repositories.InvalidFilesLengthErrorMessage}, + {name: "unsupported content type", files: []*multipart.FileHeader{fileHeader("image/gif", 1)}, want: repositories.InvalidFileExtErrorMessage}, + {name: "size at limit", files: []*multipart.FileHeader{validJPEG}}, + {name: "size over limit", files: []*multipart.FileHeader{fileHeader("image/jpeg", FILE_SIZE_IN_BYTES+1)}, want: repositories.InvalidFileSizeErrorMessage}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ValidatePost(tt.title, tt.text, tt.files); got != tt.want { + t.Fatalf("ValidatePost() = %q, want %q", got, tt.want) + } + }) + } +} +``` + +- [ ] **Step 4: Add DB-free Fiber error-path smoke tests** + +Create `controllers/threads_controller_test.go`: + +```go +package controllers + +import ( + "io" + "net/http/httptest" + "testing" + + "github.com/gofiber/fiber/v2" +) + +type testViews struct{} + +func (testViews) Load() error { return nil } + +func (testViews) Render(out io.Writer, name string, _ interface{}, _ ...string) error { + _, err := io.WriteString(out, name) + return err +} + +func TestGetThreadsRejectsInvalidPage(t *testing.T) { + app := fiber.New(fiber.Config{Views: testViews{}}) + app.Get("/", GetThreads) + + resp, err := app.Test(httptest.NewRequest("GET", "/?page=0", nil)) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + if resp.StatusCode != fiber.StatusNotFound { + t.Fatalf("status = %d, want %d", resp.StatusCode, fiber.StatusNotFound) + } +} + +func TestGetThreadRejectsInvalidID(t *testing.T) { + app := fiber.New(fiber.Config{Views: testViews{}}) + app.Get("/:threadID", GetThread) + + resp, err := app.Test(httptest.NewRequest("GET", "/invalid", nil)) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + if resp.StatusCode != fiber.StatusNotFound { + t.Fatalf("status = %d, want %d", resp.StatusCode, fiber.StatusNotFound) + } +} +``` + +- [ ] **Step 5: Run the new safety net** + +Run: + +```bash +go fmt ./... +go test ./... +go vet ./... +``` + +Expected: all tests pass and vet exits 0. + +- [ ] **Step 6: Commit the safety net** + +```bash +git add config/config_test.go utils/utils_test.go controllers/threads_controller_test.go +git commit -m "test: characterize legacy behavior" +``` + +--- + +### Task 2: Upgrade Go, Fiber v2, and Non-Database Dependencies + +**Files:** + +- Modify: `go.mod` +- Modify: `go.sum` +- Modify: `main.go` + +**Interfaces:** + +- Consumes: characterization tests from Task 1. +- Produces: Go 1.26 module baseline using Fiber v2.52.14 and HTML template v2.1.3; no route or handler API change. + +- [ ] **Step 1: Set the module Go line** + +Run: + +```bash +go mod edit -go=1.26.0 +go mod edit -toolchain=go1.26.5 +``` + +Expected: `go.mod` contains: + +```text +go 1.26.0 + +toolchain go1.26.5 +``` + +- [ ] **Step 2: Upgrade direct non-database dependencies deliberately** + +Run: + +```bash +go get github.com/gofiber/fiber/v2@v2.52.14 +go get github.com/gofiber/template/html/v2@v2.1.3 +go get github.com/dchest/captcha@v1.1.0 +go get github.com/joho/godotenv@v1.5.1 +go get github.com/disintegration/imaging@v1.6.2 +``` + +Expected: each command exits 0; Fiber remains on major version 2. + +- [ ] **Step 3: Switch the template import to its current module path** + +In `main.go`, replace: + +```go +"github.com/gofiber/template/html" +``` + +with: + +```go +"github.com/gofiber/template/html/v2" +``` + +Keep initialization unchanged: + +```go +engine := html.New("./templates", ".html") +``` + +- [ ] **Step 4: Normalize the module graph** + +Run: + +```bash +go mod tidy +go list -m all +``` + +Expected direct requirements: + +```text +github.com/dchest/captcha v1.1.0 +github.com/disintegration/imaging v1.6.2 +github.com/gofiber/fiber/v2 v2.52.14 +github.com/gofiber/template/html/v2 v2.1.3 +github.com/joho/godotenv v1.5.1 +``` + +The old root `github.com/gofiber/template` requirement must be absent. + +- [ ] **Step 5: Verify behavior on the new HTTP dependency baseline** + +Run: + +```bash +go test ./... +go vet ./... +go build ./... +``` + +Expected: all commands exit 0. + +- [ ] **Step 6: Commit the toolchain and Fiber v2 baseline** + +```bash +git add go.mod go.sum main.go +git commit -m "build: update Go and Fiber v2" +``` + +--- + +### Task 3: Migrate pgx v4 to v5 and Propagate Contexts + +**Files:** + +- Create: `repositories/files_repository_test.go` +- Create: `db/db_test.go` +- Modify: `go.mod` +- Modify: `go.sum` +- Modify: `main.go` +- Modify: `controllers/threads_controller.go` +- Modify: `repositories/posts_repository.go` +- Modify: `repositories/files_repository.go` +- Modify: `repositories/seeds.go` +- Modify: `db/db.go` + +**Interfaces:** + +- Consumes: `fiber.Ctx.UserContext() context.Context`; startup uses `context.Background()`. +- Produces: repository and DB operations accepting `context.Context`; pgx v5 transaction types; `toInt32s([]int) []int32` for PostgreSQL `integer[]` parameters. + +- [ ] **Step 1: Write the integer-array conversion test** + +Create `repositories/files_repository_test.go`: + +```go +package repositories + +import ( + "reflect" + "testing" +) + +func TestToInt32s(t *testing.T) { + tests := []struct { + name string + in []int + want []int32 + }{ + {name: "nil", in: nil, want: nil}, + {name: "empty", in: []int{}, want: []int32{}}, + {name: "values", in: []int{1, 2, 2147483647}, want: []int32{1, 2, 2147483647}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := toInt32s(tt.in); !reflect.DeepEqual(got, tt.want) { + t.Fatalf("toInt32s() = %#v, want %#v", got, tt.want) + } + }) + } +} +``` + +- [ ] **Step 2: Run the focused test and confirm the missing helper** + +Run: + +```bash +go test ./repositories -run TestToInt32s -count=1 +``` + +Expected: build failure containing `undefined: toInt32s`. + +- [ ] **Step 3: Add the minimal array conversion helper** + +Add to `repositories/files_repository.go`: + +```go +func toInt32s(values []int) []int32 { + if values == nil { + return nil + } + + result := make([]int32, len(values)) + for i, value := range values { + result[i] = int32(value) + } + return result +} +``` + +Use it directly in the query: + +```go +rows, err := Db.Pool.Query(ctx, sql, toInt32s(postIDs)) +``` + +Do not keep `pgtype.Int4Array` or the standalone `github.com/jackc/pgtype` import. + +- [ ] **Step 4: Write SQLSTATE classification tests** + +Create `db/db_test.go`: + +```go +package db + +import ( + "errors" + "testing" + + "github.com/jackc/pgx/v5/pgconn" +) + +func TestIsUndefinedTable(t *testing.T) { + tests := []struct { + name string + err error + want bool + }{ + {name: "undefined table", err: &pgconn.PgError{Code: "42P01"}, want: true}, + {name: "other postgres error", err: &pgconn.PgError{Code: "23505"}}, + {name: "ordinary error", err: errors.New("boom")}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isUndefinedTable(tt.err); got != tt.want { + t.Fatalf("isUndefinedTable() = %v, want %v", got, tt.want) + } + }) + } +} +``` + +- [ ] **Step 5: Upgrade pgx and replace imports** + +Run: + +```bash +go get github.com/jackc/pgx/v5@v5.10.0 +``` + +Replace imports exactly: + +```go +"github.com/jackc/pgx/v4" +"github.com/jackc/pgx/v4/pgxpool" +``` + +with: + +```go +"github.com/jackc/pgx/v5" +"github.com/jackc/pgx/v5/pgconn" +"github.com/jackc/pgx/v5/pgxpool" +``` + +Only import `pgconn` in `db/db.go` and `db/db_test.go`. + +- [ ] **Step 6: Define the contextual DB and repository signatures** + +Change signatures to this exact set: + +```go +// db/db.go +func Init(ctx context.Context) +func Migrate(ctx context.Context) +func runMigration(ctx context.Context, mid int, mname, msql string) +func getDbMigrations(ctx context.Context) MigrationsMap + +// repositories/files_repository.go +func (r *FilesRepository) Create(ctx context.Context, f File) error +func (r *FilesRepository) GetByPostIDs(ctx context.Context, postIDs []int) (map[int][]File, error) +func (r *FilesRepository) CreateInTx(ctx context.Context, tx pgx.Tx, f File) (int, error) + +// repositories/posts_repository.go +func (r *PostsRepository) Get(ctx context.Context, limit, offset int) ([]Post, error) +func (r *PostsRepository) GetThreadsCount(ctx context.Context) (int, error) +func (r *PostsRepository) Create(ctx context.Context, p Post) (int, error) +func (r *PostsRepository) GetThreadByPostID(ctx context.Context, ID int) ([]Post, error) +func (r *PostsRepository) GetIfThreadIsArchived(ctx context.Context, ID int) (bool, error) +func (r *PostsRepository) CreateInTx(ctx context.Context, tx pgx.Tx, p Post) (int, error) +func (r *PostsRepository) GetOldestThreadUpdatedAt(ctx context.Context) (time.Time, error) +func (r *PostsRepository) ArchiveThreadsFrom(ctx context.Context, t time.Time) error +func (r *PostsRepository) GetThreadPostsCount(ctx context.Context, id int) (int, error) +func (r *PostsRepository) BumpThreadInTx(ctx context.Context, tx pgx.Tx, id int) error + +// repositories/seeds.go +func seedDb(ctx context.Context) +func Seed(ctx context.Context) +``` + +Replace every `context.TODO()` passed to pgx with the method's `ctx`. Pass `ctx` +through internal repository calls such as: + +```go +filesMap, err := Files.GetByPostIDs(ctx, postIDs) +``` + +- [ ] **Step 7: Propagate startup and request contexts at call sites** + +In `main.go`: + +```go +ctx := context.Background() +config.Init() +db.Init(ctx) +db.Migrate(ctx) +defer db.Pool.Close() + +if config.App.IsDbSeeded { + repositories.Seed(ctx) +} +``` + +At the beginning of every controller that reaches a repository, add: + +```go +ctx := c.UserContext() +``` + +Pass `ctx` as the first argument to every repository call, `db.Pool.Acquire`, +`conn.Begin`, `tx.Rollback`, and `tx.Commit`. + +- [ ] **Step 8: Make rows lifecycle and migration error handling correct** + +After every successful `Query`, add: + +```go +defer rows.Close() +``` + +Remove pre-iteration `rows.Err()` checks. After each `for rows.Next()` loop, +repository methods returning a slice or map must return the final error: + +```go +if err := rows.Err(); err != nil { + return nil, err +} +``` + +In `getDbMigrations`, which preserves the existing panic-on-startup-error policy, +use this exact check before returning the map: + +```go +if err := rows.Err(); err != nil { + log.Panicln(err) +} +``` + +In `runMigration`, record the migration with `Exec`, not `Query`: + +```go +_, err = Pool.Exec(ctx, `INSERT INTO migrations (id, name) VALUES ($1, $2)`, mid, mname) +if err != nil { + log.Panicln(err) +} +``` + +Replace string matching with SQLSTATE classification: + +```go +func isUndefinedTable(err error) bool { + var pgErr *pgconn.PgError + return errors.As(err, &pgErr) && pgErr.Code == "42P01" +} +``` + +In `getDbMigrations`, treat only that code as an empty migration map: + +```go +rows, err := Pool.Query(ctx, `SELECT id, name FROM migrations`) +if err != nil { + if isUndefinedTable(err) { + return make(MigrationsMap) + } + log.Panicln(err) +} +defer rows.Close() +``` + +- [ ] **Step 9: Check transaction completion in both write handlers** + +Import `errors` and `github.com/jackc/pgx/v5` in +`controllers/threads_controller.go`. Replace each bare rollback defer with: + +```go +defer func() { + rollbackErr := tx.Rollback(ctx) + if rollbackErr != nil && !errors.Is(rollbackErr, pgx.ErrTxClosed) { + log.Println("error:", rollbackErr) + } +}() +``` + +Replace each unchecked commit with: + +```go +if err := tx.Commit(ctx); err != nil { + log.Println("error:", err) + return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) +} +``` + +- [ ] **Step 10: Normalize and verify the pgx v5 migration** + +Run: + +```bash +go mod tidy +go fmt ./... +go test ./... +go vet ./... +go build ./... +rg 'jackc/(pgx/v4|pgtype)|context\.TODO' --glob '*.go' +``` + +Expected: build and checks pass; final `rg` returns no matches. + +- [ ] **Step 11: Commit the database driver migration** + +```bash +git add go.mod go.sum main.go controllers/threads_controller.go repositories db +git commit -m "refactor(db): migrate to pgx v5" +``` + +--- + +### Task 4: Replace Deprecated I/O APIs + +**Files:** + +- Modify: `build/css.go` +- Modify: `files/get_files_in_folder.go` +- Modify: `gateway/connect.go` + +**Interfaces:** + +- Consumes: existing file paths, CSS rewrite behavior, and gateway registration request. +- Produces: identical behavior through `os` and `io`, with gateway response bodies closed. + +- [ ] **Step 1: Replace deprecated filesystem calls** + +In `build/css.go`, remove `io/ioutil` and use: + +```go +files, err := os.ReadDir(dir) +files, err := os.ReadDir("static/styles") +htmlTemplate, err := os.ReadFile(htmlTemplatePath) +err = os.WriteFile(htmlTemplatePath, htmlTemplate, 0644) +``` + +In `files/get_files_in_folder.go`, remove `io/ioutil` and use: + +```go +files, err := os.ReadDir(fullFolderPath) +file, err := os.ReadFile(fullFilePath) +``` + +Preserve existing return values, ordering, permissions, and error policy. + +- [ ] **Step 2: Replace gateway body reading and close the response** + +In `gateway/connect.go`, replace `io/ioutil` with `io`, then use: + +```go +resp, err := client.Do(req) +if err != nil { + log.Panicln(err) +} +defer resp.Body.Close() + +body, err := io.ReadAll(resp.Body) +if err != nil { + log.Panicln(err) +} +``` + +- [ ] **Step 3: Verify deprecated APIs are gone** + +Run: + +```bash +go fmt ./... +go test ./... +go vet ./... +go build ./... +rg 'io/ioutil|ioutil\.' --glob '*.go' +``` + +Expected: Go checks pass; final `rg` returns no matches. + +- [ ] **Step 4: Commit the compatibility cleanup** + +```bash +git add build/css.go files/get_files_in_folder.go gateway/connect.go +git commit -m "refactor: replace deprecated I/O APIs" +``` + +--- + +### Task 5: Replace Legacy Compose with Local PostgreSQL + +**Files:** + +- Modify: `docker-compose.yml` +- Modify: `.env.example` +- Modify: `Makefile` + +**Interfaces:** + +- Consumes: application `POSTGRES_URL` configuration. +- Produces: PostgreSQL at `localhost:5432`, database/user/password `micrach`, persistent volume `micrach-postgres`, and host-run development commands. + +- [ ] **Step 1: Replace Compose with the PostgreSQL-only definition** + +Replace `docker-compose.yml` with: + +```yaml +services: + db: + image: postgres:18.4-alpine3.23 + environment: + POSTGRES_DB: micrach + POSTGRES_USER: micrach + POSTGRES_PASSWORD: micrach + ports: + - "5432:5432" + volumes: + - micrach-postgres:/var/lib/postgresql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U micrach -d micrach"] + interval: 2s + timeout: 5s + retries: 15 + start_period: 5s + +volumes: + micrach-postgres: +``` + +The `/var/lib/postgresql` mount intentionally follows the PostgreSQL 18 image +layout. + +- [ ] **Step 2: Make local environment defaults safe and compatible** + +Replace `.env.example` with: + +```dotenv +ENV=development +PORT=3000 +IS_DB_SEEDED=false +IS_RATE_LIMITER_ENABLED=true +THREADS_MAX_COUNT=50 +POSTGRES_URL=postgres://micrach:micrach@localhost:5432/micrach?pool_max_conns=5 +THREAD_BUMP_LIMIT=500 +IS_CAPTCHA_ACTIVE=true +GATEWAY_URL= +GATEWAY_API_KEY= +GATEWAY_BOARD_ID= +GATEWAY_BOARD_URL= +GATEWAY_BOARD_DESCRIPTION= +``` + +- [ ] **Step 3: Add explicit host and infrastructure commands** + +Replace `Makefile` with: + +```make +.PHONY: run dev infra-up infra-down + +run: + go run . + +dev: + nodemon --exec go run . --signal SIGTERM + +infra-up: + docker compose up -d db + +infra-down: + docker compose down +``` + +- [ ] **Step 4: Validate Compose syntax and database health** + +Run: + +```bash +docker compose config +docker compose up -d db +docker compose ps +``` + +Expected: configuration is valid; `db` transitions to `healthy`. + +- [ ] **Step 5: Verify clean startup against disposable local data** + +Run with a copied local environment: + +```bash +if test ! -e .env; then cp .env.example .env; fi +go run . +``` + +Expected: logs report database and migrations online; application listens on +port 3000. Stop the process after startup verification. An existing `.env` is +never overwritten or removed, and `.env` is never staged. + +- [ ] **Step 6: Commit local infrastructure** + +```bash +git add docker-compose.yml .env.example Makefile +git commit -m "build: add local PostgreSQL compose" +``` + +--- + +### Task 6: Build a Multi-Stage Non-Root Image + +**Files:** + +- Modify: `Dockerfile` +- Create: `.dockerignore` + +**Interfaces:** + +- Consumes: Go module, templates, static assets, migrations, and writable `/app/uploads`. +- Produces: local `micrach:wave1` Linux image; no registry interaction. + +- [ ] **Step 1: Replace the Dockerfile** + +Replace `Dockerfile` with: + +```dockerfile +FROM golang:1.26.5-alpine3.24 AS build + +WORKDIR /src +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/micrach . + +FROM alpine:3.24.1 + +RUN apk add --no-cache ca-certificates \ + && addgroup -S micrach \ + && adduser -S -G micrach micrach \ + && mkdir -p /app/uploads \ + && chown -R micrach:micrach /app + +WORKDIR /app +COPY --from=build /out/micrach ./micrach +COPY --chown=micrach:micrach templates/ templates/ +COPY --chown=micrach:micrach static/ static/ +COPY --chown=micrach:micrach migrations/ migrations/ + +USER micrach +ENTRYPOINT ["./micrach"] +``` + +- [ ] **Step 2: Exclude local state from build context** + +Create `.dockerignore`: + +```text +.git +.github +.env +.env.* +!.env.example +docs +tests +uploads +micrach +*.out +*.test +``` + +- [ ] **Step 3: Build and inspect the image** + +Run: + +```bash +docker build --tag micrach:wave1 . +docker image inspect micrach:wave1 --format '{{.Config.User}} {{json .Config.Entrypoint}}' +``` + +Expected inspect output: + +```text +micrach ["./micrach"] +``` + +- [ ] **Step 4: Confirm upload storage is writable by the runtime user** + +Run: + +```bash +docker run --rm --entrypoint sh micrach:wave1 -c 'touch /app/uploads/write-test && test -f /app/uploads/write-test' +``` + +Expected: exit 0. + +- [ ] **Step 5: Commit the image build** + +```bash +git add Dockerfile .dockerignore +git commit -m "build: add multi-stage image" +``` + +--- + +### Task 7: Replace Deployment Automation with Verification CI + +**Files:** + +- Modify: `.github/workflows/push.yml` +- Delete: `.github/workflows/codesee-arch-diagram.yml` +- Create: `.github/dependabot.yml` + +**Interfaces:** + +- Consumes: `go.mod`, `go.sum`, Dockerfile, and repository tests. +- Produces: read-only checks for pushes and pull requests; reviewed dependency update pull requests. + +- [ ] **Step 1: Replace the workflow with read-only CI** + +Replace `.github/workflows/push.yml` with: + +```yaml +name: CI + +on: + push: + pull_request: + +permissions: + contents: read + +jobs: + verify: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: "1.26.5" + cache: true + cache-dependency-path: go.sum + + - name: Verify formatting + run: | + files="$(gofmt -l .)" + test -z "$files" || { printf '%s\n' "$files"; exit 1; } + + - name: Test + run: go test ./... + + - name: Vet + run: go vet ./... + + - name: Build + run: go build ./... + + - name: Build Linux binary + run: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /tmp/micrach-linux-amd64 . + + - name: Install govulncheck + run: go install golang.org/x/vuln/cmd/govulncheck@v1.6.0 + + - name: Scan reachable vulnerabilities + run: govulncheck ./... + + - name: Build container image + run: docker build --tag micrach:ci . +``` + +Do not add registry login, image push, artifact upload, SSH, environment secrets, +Swarm, or deployment steps. + +- [ ] **Step 2: Remove CodeSee** + +Delete: + +```text +.github/workflows/codesee-arch-diagram.yml +``` + +- [ ] **Step 3: Add Dependabot** + +Create `.github/dependabot.yml`: + +```yaml +version: 2 +updates: + - package-ecosystem: gomod + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 5 + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 5 +``` + +- [ ] **Step 4: Validate workflow syntax and execute its commands locally** + +Run: + +```bash +test -z "$(gofmt -l .)" +go test ./... +go vet ./... +go build ./... +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /tmp/micrach-linux-amd64 . +go run golang.org/x/vuln/cmd/govulncheck@v1.6.0 ./... +docker build --tag micrach:ci . +``` + +Expected: every command exits 0. If `govulncheck` reports a reachable +vulnerability, upgrade or document the exact blocking dependency before this +task can pass; do not suppress findings generically. + +- [ ] **Step 5: Commit CI and dependency automation** + +```bash +git add .github +git commit -m "ci: replace legacy deployment workflow" +``` + +--- + +### Task 8: Align Documentation and Agent Guidance + +**Files:** + +- Modify: `README.md` +- Modify: `AGENTS.md` + +**Interfaces:** + +- Consumes: completed Wave 1 commands and versions. +- Produces: one accurate setup and verification path for humans and coding agents. + +- [ ] **Step 1: Update README stack and prerequisites** + +State these exact baselines: + +```markdown +- Go 1.26 +- Fiber v2 +- PostgreSQL 18 for local development +``` + +Remove claims that CI uses Go 1.17 and that Docker requires a prebuilt binary. + +- [ ] **Step 2: Replace local setup with the host-run workflow** + +Document this command sequence: + +```sh +cp .env.example .env +docker compose up -d db +go run . +``` + +Document automatic reload separately: + +```sh +make dev +``` + +State that Compose runs PostgreSQL only and that nodemon is required by +`make dev`. + +- [ ] **Step 3: Replace container and CI documentation** + +Document local image verification: + +```sh +docker build -t micrach:local . +``` + +State that CI checks formatting, tests, vet, native and Linux builds, +reachable vulnerabilities, and Docker image construction. State explicitly +that CI does not publish or deploy images. + +- [ ] **Step 4: Update AGENTS guidance** + +Replace stale Go 1.15/1.17 constraints with Go 1.26. Replace the production +Docker and Swarm description with: + +```markdown +- `Dockerfile` builds the application in a pinned Go builder stage and runs it + as a non-root user in a small runtime image. +- `docker-compose.yml` starts local PostgreSQL only; run micrach on the host. +- CI verifies the project and container build but does not publish or deploy. +``` + +Keep all existing architecture, upload, migration, testing, and secret-handling +rules that remain accurate. + +- [ ] **Step 5: Verify documentation** + +Run: + +```bash +rg -n 'Go 1\.15|Go 1\.17|prebuilt|Swarm|Traefik|docker\.pkg\.github|ghcr\.io' README.md AGENTS.md .github Dockerfile docker-compose.yml +git diff --check +``` + +Expected: no stale deployment or toolchain matches; `git diff --check` exits 0. + +- [ ] **Step 6: Commit documentation** + +```bash +git add README.md AGENTS.md +git commit -m "docs: document modern development workflow" +``` + +--- + +### Task 9: Run the Wave 1 Acceptance Gate + +**Files:** + +- Verify only; modify a file only to fix a concrete failing check within Wave 1 scope. + +**Interfaces:** + +- Consumes: all Wave 1 tasks. +- Produces: fresh evidence that Wave 1 is safe to merge and that Wave 2 may be planned separately. + +- [ ] **Step 1: Verify repository hygiene** + +Run: + +```bash +git status --short +git diff --check +git ls-files .env uploads micrach '*.out' '*.test' +``` + +Expected: clean worktree after task commits; no forbidden local artifacts are +tracked. + +- [ ] **Step 2: Run the full Go gate** + +Run: + +```bash +go fmt ./... +test -z "$(gofmt -l .)" +go mod tidy +git diff --exit-code -- go.mod go.sum +go test ./... +go vet ./... +go build ./... +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /tmp/micrach-linux-amd64 . +go run golang.org/x/vuln/cmd/govulncheck@v1.6.0 ./... +``` + +Expected: every command exits 0; module files remain unchanged after tidy; +no reachable vulnerability remains unhandled. + +- [ ] **Step 3: Run the container and infrastructure gate** + +Run: + +```bash +docker compose config +docker compose up -d db +docker compose ps +docker build --tag micrach:wave1 . +docker run --rm --entrypoint sh micrach:wave1 -c 'touch /app/uploads/write-test && test -f /app/uploads/write-test' +``` + +Expected: PostgreSQL is healthy, the image builds, and the runtime user can +write uploads. + +- [ ] **Step 4: Run manual application smoke tests** + +With `.env` copied from `.env.example`, run `go run .` and verify: + +```text +GET / catalog renders +GET /?page=0 404 renders +POST / valid thread redirects +GET /:threadID thread renders +POST /:threadID valid reply redirects with fragment +GET /captcha/:captchaID PNG renders when CAPTCHA is enabled +``` + +Also verify JPEG and PNG upload limits, thumbnail creation, pagination, rate +limiting, `sage`, bump limit, and archival behavior. Inspect catalog and thread +pages at narrow and wide viewport widths; Wave 1 must introduce no visual +change. + +- [ ] **Step 5: Remove disposable local state** + +Run: + +```bash +docker compose down +rm -f /tmp/micrach-linux-amd64 +git status --short --branch +``` + +Expected: no generated or local-only file appears in Git status. Leave any +pre-existing or locally created ignored `.env` untouched. + +## Wave 2 Entry Gate + +Do not implement Fiber v3 in this plan. Start a separate spec-derived plan only +after Task 9 passes. That follow-up must recheck the current Fiber v3, template +engine, and Go requirements through Context7 and official release notes, then +cover handler context signatures, static middleware, form parsing, redirects, +CAPTCHA, uploads, pagination, rate limiting, `sage`, archival, and responsive +smoke tests. Architecture cleanup remains excluded. diff --git a/docs/superpowers/specs/2026-07-15-project-modernization-design.md b/docs/superpowers/specs/2026-07-15-project-modernization-design.md new file mode 100644 index 0000000..edcdac4 --- /dev/null +++ b/docs/superpowers/specs/2026-07-15-project-modernization-design.md @@ -0,0 +1,206 @@ +# Project Modernization Design + +Date: 2026-07-15 + +## Goal + +Modernize micrach in controlled stages without changing application behavior or +reworking its architecture. First establish a supported toolchain, current +dependencies, reproducible local infrastructure, useful CI, and a minimal test +safety net. Migrate from Fiber v2 to Fiber v3 only after that baseline is +stable. + +## Current State + +- `go.mod` declares Go 1.15, while CI uses Go 1.17. +- Fiber is v2.31.0 and pgx is v4.15.0. +- The Dockerfile packages a prebuilt binary in BusyBox; it does not build the + application. +- The GitHub Actions workflow builds, publishes an image, and deploys it to an + obsolete Docker Swarm environment over SSH. +- `docker-compose.yml` describes that obsolete Swarm, Traefik, and VPS setup. +- The CodeSee workflow and its action versions are obsolete. +- No checked-in Go tests provide a regression safety net. +- Parts of the code use deprecated `ioutil` APIs, broad `context.TODO()` calls, + and unchecked transaction completion errors. + +## Scope and Constraints + +- Preserve routes, templates, HTTP status behavior, database schema, upload + layout, and minimal-JavaScript approach. +- Preserve the current package boundaries and application architecture. +- Do not combine dependency work with handler or repository redesign. +- Do not add infrastructure that the application does not need. PostgreSQL is + the only Docker Compose service required now. +- Run the application directly on the host with `go run .` or `make dev`. +- Keep a multi-stage Dockerfile as a reproducible application build artifact, + but do not publish or deploy its image in CI. +- Do not add or modify database migrations unless an upgrade proves that a + compatibility migration is unavoidable. Such a change requires separate + review before implementation. + +## Strategy + +Use two modernization waves. Wave 1 stays on the latest compatible Fiber v2 to +avoid mixing framework API migration with the toolchain, database driver, CI, +container, and test changes. Wave 2 performs the Fiber v3 migration as a +separate change after Wave 1 is green. + +Exact dependency versions must be rechecked against Context7 and official +release notes when each wave begins. Major upgrades remain explicit; automated +dependency tooling must not silently merge them. + +## Wave 1: Stable Modern Baseline + +### Go and Dependencies + +- Set the supported Go line to Go 1.26. Use the latest available Go 1.26 patch + release in CI and the Docker builder image; keep documentation aligned. +- Upgrade Fiber from v2.31.0 to the latest stable Fiber v2 release. +- Upgrade the Fiber HTML template engine to the latest Fiber v2-compatible + release. +- Migrate `github.com/jackc/pgx/v4` and standalone + `github.com/jackc/pgtype` usage to `github.com/jackc/pgx/v5` packages. +- Replace `pgtype.Int4Array` with the pgx v5-compatible representation selected + during implementation, preserving query results and ordering. +- Upgrade other direct dependencies to current versions compatible with the + selected Go and Fiber versions. +- Run `go mod tidy` and review every direct and indirect dependency change. +- Upgrade dependencies in coherent groups so failures can be attributed to a + specific change. + +### Compatibility Cleanup + +- Replace deprecated `io/ioutil` calls with `io` and `os` equivalents. +- Replace broad `context.TODO()` usage where a request, startup, or explicit + operation context is available. +- Check and propagate transaction `Commit` errors. Preserve safe rollback + behavior. +- Limit changes to those needed for supported APIs, correct error handling, and + testability. Do not refactor package ownership or business flow. + +### Local Development Infrastructure + +- Replace the Swarm-oriented Compose file with a local PostgreSQL service. +- Give PostgreSQL a persistent named volume and a healthcheck. +- Use development-only credentials and defaults consistent with + `.env.example`. +- Do not add the application, Redis, a proxy, or other optional services to + Compose. +- Document the normal workflow: start PostgreSQL with Compose, then run micrach + on the host with `go run .` or `make dev` using nodemon. + +### Dockerfile + +- Replace the prebuilt-binary BusyBox image with a multi-stage Dockerfile. +- Build a static Linux binary in a pinned Go 1.26 builder image. +- Copy only the binary, templates, static assets, and migrations into a small + runtime image. +- Run the application as a non-root user and preserve writable upload storage. +- Do not add a container healthcheck until the application has a suitable + health endpoint. Adding such an endpoint is outside this modernization. + +### CI and Automation + +- Replace the existing build, publish, and deploy workflow with a read-only CI + workflow. +- Run formatting verification, `go test ./...`, `go vet ./...`, `go build .`, + the Linux static build, and `govulncheck ./...`. +- Build the Docker image as verification, without login, push, registry tags, + or deployment. +- Use current stable GitHub-maintained action releases and Go module caching. +- Remove SSH deployment, Docker Swarm service update, registry publishing, and + their workflow configuration. +- Remove the obsolete CodeSee workflow. +- Add Dependabot configuration for Go modules and GitHub Actions. Updates open + reviewable pull requests and are not auto-merged. + +### Test Safety Net + +Add focused tests beside changed code, favoring table-driven cases where +practical: + +- configuration parsing and defaults; +- post and upload validation boundaries; +- pure migration, filesystem, and repository helpers where they can be tested + without a shared database; +- Fiber handler smoke tests for key status and rendering paths that do not + require production data; +- pgx v5 array conversion or scanning behavior introduced by the migration. + +Tests requiring PostgreSQL must use disposable data and the local development +database only when explicitly invoked. Routine unit tests must not depend on a +developer's shared database. + +### Documentation + +- Update README prerequisites, setup, development commands, container notes, + and supported versions. +- Make `.env.example` safe for local development: no implicit external gateway + dependency and no repeated seed behavior by default. +- Update AGENTS guidance when its Go, CI, Docker, or Compose statements become + stale. + +## Wave 2: Fiber v3 Migration + +Wave 2 starts only after Wave 1 passes all automated and manual checks. + +- Recheck the current stable Fiber v3, template engine, and minimum Go + requirements through Context7 and official release notes. +- Keep Go 1.26 if supported; otherwise upgrade to the required supported Go + line as an explicit part of Wave 2. +- Change Fiber imports from v2 to v3 and use a v3-compatible template engine. +- Adapt handler context signatures, middleware configuration, static serving, + form and body parsing, redirects, and other changed APIs. +- Preserve routes, response codes, template data, forms, and browser behavior. +- Exercise catalog, thread view, thread creation, replies, CAPTCHA, pagination, + rate limiting, `sage`, archival, JPEG/PNG uploads, and thumbnail generation. +- Fix only migration regressions. Do not include architectural cleanup. +- Refresh compatible dependencies again after Fiber v3, run `go mod tidy`, and + repeat vulnerability scanning. +- Record any remaining major-version candidates or deprecated APIs for later + work instead of expanding this migration. + +## Ongoing Maintenance + +- Keep the project on a supported Go release line and consume patch releases + promptly through reviewed dependency automation. +- Review Dependabot pull requests for Go modules and GitHub Actions. +- Perform periodic dependency refreshes rather than another multi-year jump. +- Isolate future major upgrades in their own pull requests and do not mix them + with unrelated refactoring. + +## Verification and Acceptance + +Each wave must provide fresh evidence for all applicable checks: + +1. Formatting produces no diff. +2. `go test ./...` passes. +3. `go vet ./...` passes. +4. `go build .` passes. +5. The Linux static build passes. +6. `govulncheck ./...` reports no unhandled reachable vulnerability. +7. `go mod tidy` produces no unexplained diff. +8. `git diff --check` passes. +9. The Docker image builds successfully. +10. Compose PostgreSQL reaches healthy state. +11. The host-run application connects to Compose PostgreSQL and applies + migrations to a clean database. +12. Manual smoke tests cover affected pages, forms, responsive states, CAPTCHA, + pagination, rate limiting, uploads, thumbnails, `sage`, and archival. +13. No `.env`, upload, binary, credential, or generated artifact is tracked. + +Wave 1 is complete only when its checks pass and existing behavior remains +unchanged. Wave 2 cannot begin before that point. A failed major upgrade is +rolled back as its own isolated change; it must not destabilize the Wave 1 +baseline. + +## Explicit Non-Goals + +- Handler or repository architecture cleanup. +- New routes, UI redesign, or feature work. +- Database schema redesign. +- New caching, queueing, proxy, or observability services. +- Containerized local execution of the application. +- Image publishing, GitHub Container Registry integration, VPS deployment, + Docker Swarm, or Traefik configuration. diff --git a/files/get_files_in_folder.go b/files/get_files_in_folder.go index 449f31e..8783cd1 100644 --- a/files/get_files_in_folder.go +++ b/files/get_files_in_folder.go @@ -1,7 +1,6 @@ package files import ( - "io/ioutil" "log" "os" "path" @@ -15,7 +14,7 @@ func GetFullFilePathsInFolder(folder string) []string { } fullFolderPath := path.Join(currentPath, folder) - files, err := ioutil.ReadDir(fullFolderPath) + files, err := os.ReadDir(fullFolderPath) if err != nil { log.Fatal(err) } @@ -31,7 +30,7 @@ func GetFullFilePathsInFolder(folder string) []string { // Reads file contents by full path and returns string func ReadFileText(fullFilePath string) string { - file, err := ioutil.ReadFile(fullFilePath) + file, err := os.ReadFile(fullFilePath) if err != nil { log.Fatal(err) } diff --git a/gateway/connect.go b/gateway/connect.go index 2b859b2..87b5fc6 100644 --- a/gateway/connect.go +++ b/gateway/connect.go @@ -3,7 +3,7 @@ package gateway import ( "bytes" "encoding/json" - "io/ioutil" + "io" "log" "net/http" @@ -29,8 +29,9 @@ func Connect() { if err != nil { log.Panicln(err) } + defer resp.Body.Close() //We Read the response body on the line below. - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { log.Panicln(err) } diff --git a/gateway/controllers.go b/gateway/controllers.go index 418ec3a..bf1ed9a 100644 --- a/gateway/controllers.go +++ b/gateway/controllers.go @@ -7,7 +7,11 @@ import ( ) func Ping(c *fiber.Ctx) error { - headerKey := c.GetReqHeaders()["Authorization"] + headerValues := c.GetReqHeaders()["Authorization"] + headerKey := "" + if len(headerValues) > 0 { + headerKey = headerValues[len(headerValues)-1] + } if config.App.Gateway.ApiKey != headerKey { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Unauthorized"}) } diff --git a/gateway/controllers_test.go b/gateway/controllers_test.go new file mode 100644 index 0000000..3cf82d5 --- /dev/null +++ b/gateway/controllers_test.go @@ -0,0 +1,33 @@ +package gateway + +import ( + "net/http/httptest" + "testing" + + "github.com/gofiber/fiber/v2" + + "micrach/config" +) + +func TestPingUsesLastAuthorizationHeader(t *testing.T) { + previousConfig := config.App + t.Cleanup(func() { + config.App = previousConfig + }) + config.App.Gateway.ApiKey = "configured-key" + + app := fiber.New() + app.Get("/api/ping", Ping) + + req := httptest.NewRequest("GET", "/api/ping", nil) + req.Header.Add("Authorization", "wrong-key") + req.Header.Add("Authorization", "configured-key") + + resp, err := app.Test(req) + if err != nil { + t.Fatalf("test ping request: %v", err) + } + if resp.StatusCode != fiber.StatusOK { + t.Fatalf("expected status %d when final Authorization header matches, got %d", fiber.StatusOK, resp.StatusCode) + } +} diff --git a/go.mod b/go.mod index f4a81df..55350f5 100644 --- a/go.mod +++ b/go.mod @@ -1,23 +1,38 @@ module micrach -go 1.15 +go 1.26.0 + +toolchain go1.26.5 require ( - github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f + github.com/dchest/captcha v1.1.0 github.com/disintegration/imaging v1.6.2 - github.com/gofiber/fiber/v2 v2.31.0 - github.com/gofiber/template v1.6.26 - github.com/jackc/pgtype v1.10.0 - github.com/jackc/pgx/v4 v4.15.0 - github.com/joho/godotenv v1.4.0 - github.com/klauspost/compress v1.15.1 // indirect - github.com/kr/pretty v0.3.0 // indirect - github.com/lib/pq v1.10.5 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/rogpeppe/go-internal v1.8.1 // indirect - github.com/valyala/fasthttp v1.35.0 // indirect - golang.org/x/crypto v0.0.0-20220408190544-5352b0902921 // indirect - golang.org/x/image v0.0.0-20220321031419-a8550c1d254a // indirect - golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + github.com/gofiber/fiber/v2 v2.52.14 + github.com/gofiber/template/html/v2 v2.1.3 + github.com/jackc/pgx/v5 v5.10.0 + github.com/joho/godotenv v1.5.1 +) + +require ( + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/gofiber/template v1.8.3 // indirect + github.com/gofiber/utils v1.2.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/tinylib/msgp v1.2.5 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.51.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + golang.org/x/image v0.44.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.40.0 // indirect ) diff --git a/go.sum b/go.sum index 3cbc87a..d46057c 100644 --- a/go.sum +++ b/go.sum @@ -1,937 +1,73 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= -github.com/CloudyKit/jet/v6 v6.1.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= -github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= -github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= -github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= -github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/cbroglie/mustache v1.3.1/go.mod h1:SS1FTIghy0sjse4DUVGV1k/40B1qE1XkD9DtDsHo9iM= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= -github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= -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-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -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-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -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/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= 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/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M= -github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY= +github.com/dchest/captcha v1.1.0 h1:2kt47EoYUUkaISobUdTbqwx55xvKOJxyScVfw25xzhQ= +github.com/dchest/captcha v1.1.0/go.mod h1:7zoElIawLp7GUMLcj54K9kbw+jEyvz2K0FDdRRYhvWo= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= -github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= -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.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= -github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofiber/fiber/v2 v2.30.0/go.mod h1:1Ega6O199a3Y7yDGuM9FyXDPYQfv+7/y48wl6WCwUF4= -github.com/gofiber/fiber/v2 v2.31.0 h1:M2rWPQbD5fDVAjcoOLjKRXTIlHesI5Eq7I5FEQPt4Ow= -github.com/gofiber/fiber/v2 v2.31.0/go.mod h1:1Ega6O199a3Y7yDGuM9FyXDPYQfv+7/y48wl6WCwUF4= -github.com/gofiber/template v1.6.26 h1:wtbXFY1XzniCImYSBX++jVwM2aoShj8Bf7XNHojc5aQ= -github.com/gofiber/template v1.6.26/go.mod h1:Vf2XJ97b9LhBxfKbIDjk2iTxHGFCSf3TB3LcP0NDsHY= -github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/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.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -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.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -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.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= -github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= -github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= -github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= -github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= -github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.11.0 h1:HiHArx4yFbwl91X3qqIHtUFoiIfLNJXCQRsnzkiwwaQ= -github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/gofiber/fiber/v2 v2.52.14 h1:Of3L+9qVFaQNwPlcmEdl5IIodHz8BSE0j37R7rWu4pE= +github.com/gofiber/fiber/v2 v2.52.14/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= +github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc= +github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8= +github.com/gofiber/template/html/v2 v2.1.3 h1:n1LYBtmr9C0V/k/3qBblXyMxV5B0o/gpb6dFLp8ea+o= +github.com/gofiber/template/html/v2 v2.1.3/go.mod h1:U5Fxgc5KpyujU9OqKzy6Kn6Qup6Tm7zdsISR+VpnHRE= +github.com/gofiber/utils v1.2.0 h1:NCaqd+Efg3khhN++eeUUTyBz+byIxAsmIjpl8kKOMIc= +github.com/gofiber/utils v1.2.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0= +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/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns= -github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38= -github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w= -github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw= -github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.2.1 h1:gI8os0wpRXFd4FiAY2dWiqRK037tjj3t7rKFeO4X5iw= -github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= -github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.1 h1:y9FcTHGyrebwfP0ZZqFiaxTaiDnUrGkJkI+f583BL1A= -github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -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/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ= -github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-slim v0.0.0-20200618151855-bde33eecb5ee/go.mod h1:ma9TUJeni8LGZMJvOwbAv/FOwiwqIMQN570LnpqCBSM= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= -github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +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.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= +github.com/jackc/pgx/v5 v5.10.0/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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY= +github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= -github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +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/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po= +github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= 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/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0= -github.com/valyala/fasthttp v1.35.0 h1:wwkR8mZn2NbigFsaw2Zj5r+xkmzjbrA/lyTmiSlal/Y= -github.com/valyala/fasthttp v1.35.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= +github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= +github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220408190544-5352b0902921 h1:iU7T1X1J6yxDr0rda54sWGkHgOp5XJrqm79gcNlC2VM= -golang.org/x/crypto v0.0.0-20220408190544-5352b0902921/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20220321031419-a8550c1d254a h1:LnH9RNcpPv5Kzi15lXg42lYMPUf0x8CuPv1YnvBWZAg= -golang.org/x/image v0.0.0-20220321031419-a8550c1d254a/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -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-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/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-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -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-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f h1:8w7RhxzTVgUzw/AH/9mUV5q0vMgy40SQRursCcfmkCw= -golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I= +golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -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.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= -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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -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= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= 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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= -gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -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.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 8645a77..746f6d9 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "log" "strconv" "strings" @@ -11,7 +12,7 @@ import ( "github.com/gofiber/fiber/v2/middleware/compress" "github.com/gofiber/fiber/v2/middleware/limiter" "github.com/gofiber/fiber/v2/middleware/recover" - "github.com/gofiber/template/html" + "github.com/gofiber/template/html/v2" "micrach/build" "micrach/config" @@ -24,13 +25,14 @@ import ( ) func main() { + ctx := context.Background() config.Init() - db.Init() - db.Migrate() + db.Init(ctx) + db.Migrate(ctx) defer db.Pool.Close() if config.App.IsDbSeeded { - repositories.Seed() + repositories.Seed(ctx) } if config.App.Env == "production" { diff --git a/repositories/files_repository.go b/repositories/files_repository.go index dbf4c2e..4757869 100644 --- a/repositories/files_repository.go +++ b/repositories/files_repository.go @@ -4,22 +4,33 @@ import ( "context" Db "micrach/db" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" ) type FilesRepository struct{} var Files FilesRepository -func (r *FilesRepository) Create(f File) error { +func toInt32s(values []int) []int32 { + if values == nil { + return nil + } + + result := make([]int32, len(values)) + for i, value := range values { + result[i] = int32(value) + } + return result +} + +func (r *FilesRepository) Create(ctx context.Context, f File) error { sql := ` INSERT INTO files (post_id, name, ext, size) VALUES ($1, $2, $3, $4) ` row := Db.Pool.QueryRow( - context.TODO(), sql, f.PostID, f.Name, f.Ext, f.Size, + ctx, sql, f.PostID, f.Name, f.Ext, f.Size, ) err := row.Scan() @@ -30,7 +41,7 @@ func (r *FilesRepository) Create(f File) error { return nil } -func (r *FilesRepository) GetByPostIDs(postIDs []int) (map[int][]File, error) { +func (r *FilesRepository) GetByPostIDs(ctx context.Context, postIDs []int) (map[int][]File, error) { sql := ` SELECT id, post_id, name, size, ext FROM files @@ -38,16 +49,11 @@ func (r *FilesRepository) GetByPostIDs(postIDs []int) (map[int][]File, error) { ORDER BY id ASC ` - ids := &pgtype.Int4Array{} - ids.Set(postIDs) - - rows, err := Db.Pool.Query(context.TODO(), sql, ids) + rows, err := Db.Pool.Query(ctx, sql, toInt32s(postIDs)) if err != nil { return nil, err } - if rows.Err() != nil { - return nil, rows.Err() - } + defer rows.Close() filesMapByPostId := make(map[int][]File) for rows.Next() { @@ -66,11 +72,14 @@ func (r *FilesRepository) GetByPostIDs(postIDs []int) (map[int][]File, error) { file, ) } + if err := rows.Err(); err != nil { + return nil, err + } return filesMapByPostId, nil } -func (r *FilesRepository) CreateInTx(tx pgx.Tx, f File) (int, error) { +func (r *FilesRepository) CreateInTx(ctx context.Context, tx pgx.Tx, f File) (int, error) { sql := ` INSERT INTO files (post_id, name, ext, size) VALUES ($1, $2, $3, $4) @@ -78,7 +87,7 @@ func (r *FilesRepository) CreateInTx(tx pgx.Tx, f File) (int, error) { ` row := tx.QueryRow( - context.TODO(), sql, f.PostID, f.Name, f.Ext, f.Size, + ctx, sql, f.PostID, f.Name, f.Ext, f.Size, ) createdFile := new(File) diff --git a/repositories/files_repository_test.go b/repositories/files_repository_test.go new file mode 100644 index 0000000..265bf2e --- /dev/null +++ b/repositories/files_repository_test.go @@ -0,0 +1,26 @@ +package repositories + +import ( + "reflect" + "testing" +) + +func TestToInt32s(t *testing.T) { + tests := []struct { + name string + in []int + want []int32 + }{ + {name: "nil", in: nil, want: nil}, + {name: "empty", in: []int{}, want: []int32{}}, + {name: "values", in: []int{1, 2, 2147483647}, want: []int32{1, 2, 2147483647}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := toInt32s(tt.in); !reflect.DeepEqual(got, tt.want) { + t.Fatalf("toInt32s() = %#v, want %#v", got, tt.want) + } + }) + } +} diff --git a/repositories/posts_repository.go b/repositories/posts_repository.go index 309bbfa..55be5e4 100644 --- a/repositories/posts_repository.go +++ b/repositories/posts_repository.go @@ -6,14 +6,14 @@ import ( Db "micrach/db" "time" - "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v5" ) type PostsRepository struct{} var Posts PostsRepository -func (r *PostsRepository) Get(limit, offset int) ([]Post, error) { +func (r *PostsRepository) Get(ctx context.Context, limit, offset int) ([]Post, error) { sql := ` SELECT id, title, text, created_at FROM posts @@ -25,13 +25,11 @@ func (r *PostsRepository) Get(limit, offset int) ([]Post, error) { LIMIT $2 ` - rows, err := Db.Pool.Query(context.TODO(), sql, offset, limit) + rows, err := Db.Pool.Query(ctx, sql, offset, limit) if err != nil { return nil, err } - if rows.Err() != nil { - return nil, err - } + defer rows.Close() postsMap := make(map[int]Post) var postIDs []int @@ -45,8 +43,11 @@ func (r *PostsRepository) Get(limit, offset int) ([]Post, error) { postsMap[post.ID] = post postIDs = append(postIDs, post.ID) } + if err := rows.Err(); err != nil { + return nil, err + } - filesMap, err := Files.GetByPostIDs(postIDs) + filesMap, err := Files.GetByPostIDs(ctx, postIDs) if err != nil { return nil, err } @@ -61,7 +62,7 @@ func (r *PostsRepository) Get(limit, offset int) ([]Post, error) { return posts, nil } -func (r *PostsRepository) GetThreadsCount() (int, error) { +func (r *PostsRepository) GetThreadsCount(ctx context.Context) (int, error) { sql := ` SELECT COUNT(*) FROM posts @@ -71,7 +72,7 @@ func (r *PostsRepository) GetThreadsCount() (int, error) { AND is_archived != true ` - row := Db.Pool.QueryRow(context.TODO(), sql) + row := Db.Pool.QueryRow(ctx, sql) var count int err := row.Scan(&count) if err != nil { @@ -80,7 +81,7 @@ func (r *PostsRepository) GetThreadsCount() (int, error) { return count, nil } -func (r *PostsRepository) Create(p Post) (int, error) { +func (r *PostsRepository) Create(ctx context.Context, p Post) (int, error) { sql := ` INSERT INTO posts (is_parent, parent_id, title, text, is_sage, updated_at) VALUES ($1, $2, $3, $4, $5, $6) @@ -90,11 +91,11 @@ func (r *PostsRepository) Create(p Post) (int, error) { var row pgx.Row if p.IsParent { row = Db.Pool.QueryRow( - context.TODO(), sql, p.IsParent, nil, p.Title, p.Text, p.IsSage, time.Now(), + ctx, sql, p.IsParent, nil, p.Title, p.Text, p.IsSage, time.Now(), ) } else { row = Db.Pool.QueryRow( - context.TODO(), sql, p.IsParent, p.ParentID, p.Title, p.Text, p.IsSage, nil, + ctx, sql, p.IsParent, p.ParentID, p.Title, p.Text, p.IsSage, nil, ) } @@ -107,7 +108,7 @@ func (r *PostsRepository) Create(p Post) (int, error) { return createdPost.ID, nil } -func (r *PostsRepository) GetThreadByPostID(ID int) ([]Post, error) { +func (r *PostsRepository) GetThreadByPostID(ctx context.Context, ID int) ([]Post, error) { sql := ` SELECT id, @@ -124,13 +125,11 @@ func (r *PostsRepository) GetThreadByPostID(ID int) ([]Post, error) { ORDER BY created_at ASC ` - rows, err := Db.Pool.Query(context.TODO(), sql, ID) + rows, err := Db.Pool.Query(ctx, sql, ID) if err != nil { return nil, err } - if rows.Err() != nil { - return nil, err - } + defer rows.Close() postsMap := make(map[int]Post) var postIDs []int @@ -152,8 +151,11 @@ func (r *PostsRepository) GetThreadByPostID(ID int) ([]Post, error) { postsMap[post.ID] = post postIDs = append(postIDs, post.ID) } + if err := rows.Err(); err != nil { + return nil, err + } - filesMap, err := Files.GetByPostIDs(postIDs) + filesMap, err := Files.GetByPostIDs(ctx, postIDs) if err != nil { return nil, err } @@ -169,7 +171,7 @@ func (r *PostsRepository) GetThreadByPostID(ID int) ([]Post, error) { } // Check if thread is archived -func (r *PostsRepository) GetIfThreadIsArchived(ID int) (bool, error) { +func (r *PostsRepository) GetIfThreadIsArchived(ctx context.Context, ID int) (bool, error) { sql := ` SELECT is_archived FROM posts @@ -177,7 +179,7 @@ func (r *PostsRepository) GetIfThreadIsArchived(ID int) (bool, error) { LIMIT 1 ` - row := Db.Pool.QueryRow(context.TODO(), sql, ID) + row := Db.Pool.QueryRow(ctx, sql, ID) var isArchived bool err := row.Scan(&isArchived) if err != nil { @@ -186,7 +188,7 @@ func (r *PostsRepository) GetIfThreadIsArchived(ID int) (bool, error) { return isArchived, nil } -func (r *PostsRepository) CreateInTx(tx pgx.Tx, p Post) (int, error) { +func (r *PostsRepository) CreateInTx(ctx context.Context, tx pgx.Tx, p Post) (int, error) { sql := ` INSERT INTO posts (is_parent, parent_id, title, text, is_sage, updated_at) VALUES ($1, $2, $3, $4, $5, $6) @@ -196,11 +198,11 @@ func (r *PostsRepository) CreateInTx(tx pgx.Tx, p Post) (int, error) { var row pgx.Row if p.IsParent { row = tx.QueryRow( - context.TODO(), sql, p.IsParent, nil, p.Title, p.Text, p.IsSage, time.Now(), + ctx, sql, p.IsParent, nil, p.Title, p.Text, p.IsSage, time.Now(), ) } else { row = tx.QueryRow( - context.TODO(), sql, p.IsParent, p.ParentID, p.Title, p.Text, p.IsSage, nil, + ctx, sql, p.IsParent, p.ParentID, p.Title, p.Text, p.IsSage, nil, ) } @@ -213,7 +215,7 @@ func (r *PostsRepository) CreateInTx(tx pgx.Tx, p Post) (int, error) { return createdPost.ID, nil } -func (r *PostsRepository) GetOldestThreadUpdatedAt() (time.Time, error) { +func (r *PostsRepository) GetOldestThreadUpdatedAt(ctx context.Context) (time.Time, error) { sql := ` SELECT updated_at FROM posts @@ -226,7 +228,7 @@ func (r *PostsRepository) GetOldestThreadUpdatedAt() (time.Time, error) { LIMIT 1 ` - row := Db.Pool.QueryRow(context.TODO(), sql, Config.App.ThreadsMaxCount) + row := Db.Pool.QueryRow(ctx, sql, Config.App.ThreadsMaxCount) var updatedAt time.Time err := row.Scan(&updatedAt) if err != nil { @@ -235,7 +237,7 @@ func (r *PostsRepository) GetOldestThreadUpdatedAt() (time.Time, error) { return updatedAt, nil } -func (r *PostsRepository) ArchiveThreadsFrom(t time.Time) error { +func (r *PostsRepository) ArchiveThreadsFrom(ctx context.Context, t time.Time) error { sql := ` UPDATE posts SET is_archived = true @@ -245,12 +247,12 @@ func (r *PostsRepository) ArchiveThreadsFrom(t time.Time) error { AND updated_at <= $1 ` - _, err := Db.Pool.Exec(context.TODO(), sql, t) + _, err := Db.Pool.Exec(ctx, sql, t) return err } // Returns count of posts in thread by thread ID -func (r *PostsRepository) GetThreadPostsCount(id int) (int, error) { +func (r *PostsRepository) GetThreadPostsCount(ctx context.Context, id int) (int, error) { sql := ` SELECT COUNT(*) FROM posts @@ -259,7 +261,7 @@ func (r *PostsRepository) GetThreadPostsCount(id int) (int, error) { OR (parent_id = $1 AND is_deleted != true) ` - row := Db.Pool.QueryRow(context.TODO(), sql, id) + row := Db.Pool.QueryRow(ctx, sql, id) var count int err := row.Scan(&count) if err != nil { @@ -269,13 +271,13 @@ func (r *PostsRepository) GetThreadPostsCount(id int) (int, error) { } // Updates threads updated at time by thread ID -func (r *PostsRepository) BumpThreadInTx(tx pgx.Tx, id int) error { +func (r *PostsRepository) BumpThreadInTx(ctx context.Context, tx pgx.Tx, id int) error { sql := ` UPDATE posts SET updated_at = now() WHERE id = $1 ` - _, err := tx.Exec(context.TODO(), sql, id) + _, err := tx.Exec(ctx, sql, id) return err } diff --git a/repositories/seeds.go b/repositories/seeds.go index aea9148..67233f6 100644 --- a/repositories/seeds.go +++ b/repositories/seeds.go @@ -1,6 +1,7 @@ package repositories import ( + "context" "log" "math/rand" "time" @@ -63,7 +64,7 @@ func getPost(id int, pid *int) Post { } } -func seedDb() { +func seedDb(ctx context.Context) { // preparing seed data with parent posts with files var parentPosts []Post for i := 1; i < 10; i++ { @@ -73,7 +74,7 @@ func seedDb() { for _, parentPost := range parentPosts { // saving parent post in db - parentPostID, err := Posts.Create(parentPost) + parentPostID, err := Posts.Create(ctx, parentPost) if err != nil { log.Panicln(err) } @@ -81,7 +82,7 @@ func seedDb() { // saving parent post files for _, file := range parentPost.Files { file.PostID = parentPostID - err = Files.Create(file) + err = Files.Create(ctx, file) if err != nil { log.Panicln(err) } @@ -91,7 +92,7 @@ func seedDb() { for i := 0; i < 10; i++ { // getting child post with files childPost := getPost(0, &parentPostID) - childPostID, err := Posts.Create(childPost) + childPostID, err := Posts.Create(ctx, childPost) if err != nil { log.Panicln(err) } @@ -99,7 +100,7 @@ func seedDb() { // saving child post files for _, file := range childPost.Files { file.PostID = childPostID - err = Files.Create(file) + err = Files.Create(ctx, file) if err != nil { log.Panicln(err) } @@ -109,7 +110,7 @@ func seedDb() { } } -func Seed() { - seedDb() +func Seed(ctx context.Context) { + seedDb(ctx) log.Println("seeds - online") } diff --git a/utils/utils_test.go b/utils/utils_test.go new file mode 100644 index 0000000..1f58b11 --- /dev/null +++ b/utils/utils_test.go @@ -0,0 +1,48 @@ +package utils + +import ( + "mime/multipart" + "net/textproto" + "strings" + "testing" + + "micrach/repositories" +) + +func fileHeader(contentType string, size int64) *multipart.FileHeader { + header := make(textproto.MIMEHeader) + header.Set("Content-Type", contentType) + return &multipart.FileHeader{Filename: "upload", Header: header, Size: size} +} + +func TestValidatePost(t *testing.T) { + validPNG := fileHeader("image/png", 1) + validJPEG := fileHeader("image/jpeg", FILE_SIZE_IN_BYTES) + + tests := []struct { + name string + title string + text string + files []*multipart.FileHeader + want string + }{ + {name: "text only", text: "hello"}, + {name: "file only", files: []*multipart.FileHeader{validPNG}}, + {name: "empty", want: repositories.InvalidTextOrFilesErrorMessage}, + {name: "title over limit", title: strings.Repeat("я", 101), text: "ok", want: repositories.InvalidTitleLengthErrorMessage}, + {name: "text over limit", text: strings.Repeat("я", 1001), want: repositories.InvalidTextLengthErrorMessage}, + {name: "four files", files: []*multipart.FileHeader{validPNG, validPNG, validJPEG, validJPEG}}, + {name: "five files", files: []*multipart.FileHeader{validPNG, validPNG, validPNG, validPNG, validPNG}, want: repositories.InvalidFilesLengthErrorMessage}, + {name: "unsupported content type", files: []*multipart.FileHeader{fileHeader("image/gif", 1)}, want: repositories.InvalidFileExtErrorMessage}, + {name: "size at limit", files: []*multipart.FileHeader{validJPEG}}, + {name: "size over limit", files: []*multipart.FileHeader{fileHeader("image/jpeg", FILE_SIZE_IN_BYTES+1)}, want: repositories.InvalidFileSizeErrorMessage}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ValidatePost(tt.title, tt.text, tt.files); got != tt.want { + t.Fatalf("ValidatePost() = %q, want %q", got, tt.want) + } + }) + } +}