diff --git a/.dockerignore b/.dockerignore index 2bd086d3e..fa83e261b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,3 +7,5 @@ win-wpf/ mattermost-plugin/ website/ linux/ +go.work +go.work.sum diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 94a8383be..e69de29bb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +0,0 @@ -* @mattermost/core-focalboard diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 73b75e20e..8b66ab562 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,10 @@ on: pull_request: workflow_dispatch: +env: + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + EXCLUDE_ENTERPRISE: true + jobs: ci-ubuntu-server: @@ -23,26 +27,57 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 - + with: + path: "focalboard" + - id: "mattermostServer" + uses: actions/checkout@v3 + continue-on-error: true + with: + repository: "mattermost/mattermost-server" + fetch-depth: "20" + path: "mattermost-server" + ref: ${{ env.BRANCH_NAME }} + - uses: actions/checkout@v3 + if: steps.mattermostServer.outcome == 'failure' + with: + repository: "mattermost/mattermost-server" + fetch-depth: "20" + path: "mattermost-server" + ref : "master" - name: Set up Go uses: actions/setup-go@v3 with: go-version: 1.18.1 - name: "Test server: ${{matrix['db']}}" - run: make server-test-${{matrix['db']}} + run: cd focalboard; make server-test-${{matrix['db']}} ci-ubuntu-webapp: runs-on: ubuntu-18.04 - steps: - name: Checkout uses: actions/checkout@v3 - + with: + path: "focalboard" + - id: "mattermostServer" + uses: actions/checkout@v3 + continue-on-error: true + with: + repository: "mattermost/mattermost-server" + fetch-depth: "20" + path: "mattermost-server" + ref: ${{ env.BRANCH_NAME }} + - uses: actions/checkout@v3 + if: steps.mattermostServer.outcome == 'failure' + with: + repository: "mattermost/mattermost-server" + fetch-depth: "20" + path: "mattermost-server" + ref : "master" - name: npm ci run: | - cd webapp && npm ci && cd - - cd mattermost-plugin/webapp && npm ci + cd focalboard/webapp && npm ci && cd - + cd focalboard/mattermost-plugin/webapp && npm ci - name: Set up Go uses: actions/setup-go@v3 @@ -55,19 +90,19 @@ jobs: node-version: 16.1.0 - name: Build Linux server - run: make server-linux-package + run: cd focalboard; make server-linux-package - name: Copy server binary for Cypress - run: cp bin/linux/focalboard-server bin/ + run: cp focalboard/bin/linux/focalboard-server focalboard/bin/ - name: Upload server package uses: actions/upload-artifact@v1 with: name: focalboard-server-linux-amd64.tar.gz - path: ${{ github.workspace }}/dist/focalboard-server-linux-amd64.tar.gz + path: ${{ github.workspace }}/focalboard/dist/focalboard-server-linux-amd64.tar.gz - name: Lint & test webapp - run: make webapp-ci + run: cd focalboard; make webapp-ci ci-windows-server: runs-on: windows-2022 @@ -80,6 +115,23 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 + with: + path: "focalboard" + - id: "mattermostServer" + uses: actions/checkout@v3 + continue-on-error: true + with: + repository: "mattermost/mattermost-server" + fetch-depth: "20" + path: "mattermost-server" + ref: ${{ env.BRANCH_NAME }} + - uses: actions/checkout@v3 + if: steps.mattermostServer.outcome == 'failure' + with: + repository: "mattermost/mattermost-server" + fetch-depth: "20" + path: "mattermost-server" + ref : "master" - name: Set up Go uses: actions/setup-go@v3 @@ -87,7 +139,7 @@ jobs: go-version: 1.18.1 - name: "Test server (minimum): ${{matrix['db']}}" - run: make server-test-mini-${{matrix['db']}} + run: cd focalboard; make server-test-mini-${{matrix['db']}} ci-mac-server: runs-on: macos-11 @@ -100,6 +152,23 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 + with: + path: "focalboard" + - id: "mattermostServer" + uses: actions/checkout@v3 + continue-on-error: true + with: + repository: "mattermost/mattermost-server" + fetch-depth: "20" + path: "mattermost-server" + ref: ${{ env.BRANCH_NAME }} + - uses: actions/checkout@v3 + if: steps.mattermostServer.outcome == 'failure' + with: + repository: "mattermost/mattermost-server" + fetch-depth: "20" + path: "mattermost-server" + ref : "master" - name: Set up Go uses: actions/setup-go@v3 @@ -107,4 +176,4 @@ jobs: go-version: 1.18.1 - name: "Test server (minimum): ${{matrix['db']}}" - run: make server-test-mini-${{matrix['db']}} + run: cd focalboard; make server-test-mini-${{matrix['db']}} diff --git a/.github/workflows/dev-release.yml b/.github/workflows/dev-release.yml index 9bf05309c..2376c1f1d 100644 --- a/.github/workflows/dev-release.yml +++ b/.github/workflows/dev-release.yml @@ -7,29 +7,49 @@ on: branches: [ main, release-** ] workflow_dispatch: +env: + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + EXCLUDE_ENTERPRISE: true + jobs: ubuntu: runs-on: ubuntu-18.04 - + steps: - - name: Checkout + - uses: actions/checkout@v3 + with: + path: "focalboard" + - id: "mattermostServer" uses: actions/checkout@v3 + continue-on-error: true + with: + repository: "mattermost/mattermost-server" + fetch-depth: "20" + path: "mattermost-server" + ref: ${{ env.BRANCH_NAME }} + - uses: actions/checkout@v3 + if: steps.mattermostServer.outcome == 'failure' + with: + repository: "mattermost/mattermost-server" + fetch-depth: "20" + path: "mattermost-server" + ref : "master" - name: Replace token 1 server - run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go + run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go - name: Replace token 1 webapp - run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/mattermost-plugin/webapp/src/index.tsx - name: Replace token 2 server - run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go + run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go - name: Replace token 2 webapp - run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/focalboard/mattermost-plugin/webapp/src/index.tsx - name: npm ci - run: cd webapp; npm ci --no-optional + run: cd focalboard/webapp; npm ci --no-optional - name: Set up Go uses: actions/setup-go@v3 @@ -51,7 +71,7 @@ jobs: run: sudo apt-get install libwebkit2gtk-4.0-dev - name: Build Linux server and app - run: make server-linux-package linux-app + run: cd focalboard/; make server-linux-package linux-app env: BUILD_NUMBER: ${{ github.run_id }} @@ -59,13 +79,13 @@ jobs: uses: actions/upload-artifact@v3 with: name: focalboard-server-linux-amd64.tar.gz - path: ${{ github.workspace }}/dist/focalboard-server-linux-amd64.tar.gz + path: ${{ github.workspace }}/focalboard/dist/focalboard-server-linux-amd64.tar.gz - name: Upload app package uses: actions/upload-artifact@v3 with: name: focalboard-linux.tar.gz - path: ${{ github.workspace }}/linux/dist/focalboard-linux.tar.gz + path: ${{ github.workspace }}/focalboard/linux/dist/focalboard-linux.tar.gz macos: runs-on: macos-11 @@ -74,21 +94,37 @@ jobs: - name: Checkout uses: actions/checkout@v3 - + with: + path: "focalboard" + - id: "mattermostServer" + uses: actions/checkout@v3 + continue-on-error: true + with: + repository: "mattermost/mattermost-server" + fetch-depth: "20" + path: "mattermost-server" + ref: ${{ env.BRANCH_NAME }} + - uses: actions/checkout@v3 + if: steps.mattermostServer.outcome == 'failure' + with: + repository: "mattermost/mattermost-server" + fetch-depth: "20" + path: "mattermost-server" + ref : "master" - name: Replace token 1 server - run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go + run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go - name: Replace token 1 webapp - run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/mattermost-plugin/webapp/src/index.tsx - name: Replace token 2 server - run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go + run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go - name: Replace token 2 webapp - run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/focalboard/mattermost-plugin/webapp/src/index.tsx - name: npm ci - run: cd webapp; npm ci --no-optional + run: cd focalboard/webapp; npm ci --no-optional - name: Set up Go uses: actions/setup-go@v3 @@ -99,7 +135,7 @@ jobs: run: ls -n /Applications/ | grep Xcode* - name: Build macOS - run: make mac-app + run: cd focalboard; make mac-app env: DEVELOPER_DIR: /Applications/Xcode_13.2.1.app/Contents/Developer BUILD_NUMBER: ${{ github.run_id }} @@ -108,7 +144,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: focalboard-mac.zip - path: ${{ github.workspace }}/mac/dist/focalboard-mac.zip + path: ${{ github.workspace }}/focalboard/mac/dist/focalboard-mac.zip windows: runs-on: windows-2022 @@ -116,24 +152,40 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 - + with: + path: "focalboard" + - id: "mattermostServer" + uses: actions/checkout@v3 + continue-on-error: true + with: + repository: "mattermost/mattermost-server" + fetch-depth: "20" + path: "mattermost-server" + ref: ${{ env.BRANCH_NAME }} + - uses: actions/checkout@v3 + if: steps.mattermostServer.outcome == 'failure' + with: + repository: "mattermost/mattermost-server" + fetch-depth: "20" + path: "mattermost-server" + ref : "master" - name: Replace token 1 server - run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go + run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go - name: Replace token 1 webapp - run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/mattermost-plugin/webapp/src/index.tsx - name: Replace token 2 server - run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go + run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go - name: Replace token 2 webapp - run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/focalboard/mattermost-plugin/webapp/src/index.tsx - name: Add msbuild to PATH uses: microsoft/setup-msbuild@v1.1 - name: npm ci - run: cd webapp; npm ci --no-optional + run: cd focalboard/webapp; npm ci --no-optional - name: Set up Go uses: actions/setup-go@v3 @@ -146,10 +198,10 @@ jobs: nuget-version: '5.x' - name: NuGet Restore - run: nuget restore win-wpf\Focalboard.sln + run: nuget restore focalboard\win-wpf\Focalboard.sln - name: Build Windows WPF app - run: make win-wpf-app + run: cd focalboard; make win-wpf-app env: BUILD_NUMBER: ${{ github.run_id }} @@ -157,35 +209,51 @@ jobs: uses: actions/upload-artifact@v3 with: name: focalboard.msix - path: ${{ github.workspace }}/win-wpf/focalboard.msix + path: ${{ github.workspace }}/focalboard/win-wpf/focalboard.msix - name: Upload app zip package uses: actions/upload-artifact@v3 with: name: focalboard-win.zip - path: ${{ github.workspace }}/win-wpf/dist/focalboard-win.zip + path: ${{ github.workspace }}/focalboard/win-wpf/dist/focalboard-win.zip plugin: runs-on: ubuntu-18.04 steps: - - name: Checkout + - uses: actions/checkout@v3 + with: + path: "focalboard" + - id: "mattermostServer" uses: actions/checkout@v3 + continue-on-error: true + with: + repository: "mattermost/mattermost-server" + fetch-depth: "20" + path: "mattermost-server" + ref: ${{ env.BRANCH_NAME }} + - uses: actions/checkout@v3 + if: steps.mattermostServer.outcome == 'failure' + with: + repository: "mattermost/mattermost-server" + fetch-depth: "20" + path: "mattermost-server" + ref : "master" - name: Replace token 1 server - run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go + run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go - name: Replace token 1 webapp - run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/mattermost-plugin/webapp/src/index.tsx - name: Replace token 2 server - run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/server/services/telemetry/telemetry.go + run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go - name: Replace token 2 webapp - run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/mattermost-plugin/webapp/src/index.tsx + run: sed -i -e "s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g" ${{ github.workspace }}/focalboard/mattermost-plugin/webapp/src/index.tsx - name: npm ci - run: cd webapp; npm ci --no-optional + run: cd focalboard/webapp; npm ci --no-optional - name: Set up Go uses: actions/setup-go@v3 @@ -198,21 +266,21 @@ jobs: node-version: 16.1.0 - name: Build webapp - run: make webapp + run: cd focalboard; make webapp - name: npm ci plugin dependencies - run: cd mattermost-plugin/webapp; npm ci --no-optional + run: cd focalboard/mattermost-plugin/webapp; npm ci --no-optional - name: Build plugin - run: cd mattermost-plugin; make dist + run: cd focalboard/mattermost-plugin; make dist env: BUILD_NUMBER: ${{ github.run_id }} - name: Rename plugin file - run: cd mattermost-plugin/dist; mv focalboard-*.tar.gz mattermost-plugin-focalboard.tar.gz + run: cd focalboard/mattermost-plugin/dist; mv focalboard-*.tar.gz mattermost-plugin-focalboard.tar.gz - name: Upload plugin artifact uses: actions/upload-artifact@v3 with: name: mattermost-plugin-focalboard.tar.gz - path: ${{ github.workspace }}/mattermost-plugin/dist/mattermost-plugin-focalboard.tar.gz + path: ${{ github.workspace }}/focalboard/mattermost-plugin/dist/mattermost-plugin-focalboard.tar.gz diff --git a/.github/workflows/lint-server.yml b/.github/workflows/lint-server.yml index 4ffc48a12..32e237db5 100644 --- a/.github/workflows/lint-server.yml +++ b/.github/workflows/lint-server.yml @@ -7,6 +7,10 @@ on: branches: [ main, release-** ] workflow_dispatch: +env: + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + EXCLUDE_ENTERPRISE: true + jobs: golangci: name: plugin @@ -16,7 +20,26 @@ jobs: with: go-version: 1.18.1 - uses: actions/checkout@v3 + with: + path: "focalboard" + - id: "mattermostServer" + uses: actions/checkout@v3 + continue-on-error: true + with: + repository: "mattermost/mattermost-server" + fetch-depth: "20" + path: "mattermost-server" + ref: ${{ env.BRANCH_NAME }} + - uses: actions/checkout@v3 + if: steps.mattermostServer.outcome == 'failure' + with: + repository: "mattermost/mattermost-server" + fetch-depth: "20" + path: "mattermost-server" + ref : "master" - name: set up golangci-lint run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.46.2 - name: lint - run: make server-lint + run: | + cd focalboard + make server-lint diff --git a/.github/workflows/prod-release.yml b/.github/workflows/prod-release.yml index 792704a83..d08e246d4 100644 --- a/.github/workflows/prod-release.yml +++ b/.github/workflows/prod-release.yml @@ -2,6 +2,10 @@ name: Production-Release on: workflow_dispatch +env: + EXCLUDE_SERVER: true + EXCLUDE_ENTERPRISE: true + jobs: ubuntu: diff --git a/Makefile b/Makefile index 1551f6603..3db08656b 100644 --- a/Makefile +++ b/Makefile @@ -39,8 +39,9 @@ prebuild: ## Run prebuild actions (install dependencies etc.). ci: webapp-ci server-test ## Simulate CI, locally. +setup-go-work: export EXCLUDE_ENTERPRISE ?= true setup-go-work: ## Sets up a go.work file - go run ./mattermost-plugin/build/gowork/main.go + go run ./build/gowork/main.go templates-archive: setup-go-work ## Build templates archive file cd server/assets/build-template-archive; go run -tags '$(BUILD_TAGS)' main.go --dir="../templates-boardarchive" --out="../templates.boardarchive" diff --git a/mattermost-plugin/build/gowork/main.go b/build/gowork/main.go similarity index 82% rename from mattermost-plugin/build/gowork/main.go rename to build/gowork/main.go index 70414d273..c962369d5 100644 --- a/mattermost-plugin/build/gowork/main.go +++ b/build/gowork/main.go @@ -39,22 +39,30 @@ func main() { } fmt.Fprintln(os.Stdout, "go.work written successfully.") + fmt.Fprintln(os.Stdout, content) } func makeGoWork(ci bool) string { + repos := map[string]string{ + "../mattermost-server": "EXCLUDE_SERVER", + "../enterprise": "EXCLUDE_ENTERPRISE", + "./mattermost-plugin": "EXCLUDE_PLUGIN", + } + var b strings.Builder b.WriteString("go 1.18\n\n") - b.WriteString("use ./mattermost-plugin\n") b.WriteString("use ./server\n") + for repo, envVarName := range repos { + if !isEnvVarTrue(envVarName, true) { + b.WriteString(fmt.Sprintf("use %s\n", repo)) + } + } + if ci { b.WriteString("use ./linux\n") - } else { - b.WriteString("use ../mattermost-server\n") - b.WriteString("use ../enterprise\n") } - return b.String() } diff --git a/docker/Dockerfile b/docker/Dockerfile index 231cc649b..eb7a054e7 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -13,7 +13,8 @@ FROM golang:1.18.3@sha256:b203dc573d81da7b3176264bfa447bd7c10c9347689be405403818 WORKDIR /go/src/focalboard ADD . /go/src/focalboard -RUN make server-linux + +RUN EXCLUDE_PLUGIN=true EXCLUDE_SERVER=true EXCLUDE_ENTERPRISE=true make server-linux RUN mkdir /data ## Final image diff --git a/linux/go.mod b/linux/go.mod index 8c5e54ee0..abcb9e34b 100644 --- a/linux/go.mod +++ b/linux/go.mod @@ -7,7 +7,7 @@ replace github.com/mattermost/focalboard/server => ../server require ( github.com/google/uuid v1.3.0 github.com/mattermost/focalboard/server v0.0.0-00010101000000-000000000000 - github.com/mattermost/mattermost-server/v6 v6.0.0-20220711175838-7ee7523729e6 + github.com/mattermost/mattermost-server/v6 v6.0.0-20220802151854-f07c31c5d933 github.com/webview/webview v0.0.0-20220314230258-a2b7746141c3 ) @@ -15,10 +15,11 @@ require ( github.com/Masterminds/squirrel v1.5.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect + github.com/blang/semver/v4 v4.0.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.0 // indirect - github.com/dyatlov/go-opengraph v0.0.0-20210112100619-dae8665a5b09 // indirect + github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a // indirect github.com/fatih/color v1.13.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect @@ -28,7 +29,10 @@ require ( github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/graph-gophers/graphql-go v1.4.0 // indirect + github.com/hashicorp/go-hclog v1.2.1 // indirect + github.com/hashicorp/go-plugin v1.4.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/klauspost/compress v1.15.6 // indirect @@ -41,7 +45,7 @@ require ( github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d // indirect github.com/mattermost/logr/v2 v2.0.15 // indirect - github.com/mattermost/mattermost-plugin-api v0.0.28-0.20220623051512-0afd85e854d4 // indirect + github.com/mattermost/mattermost-plugin-api v0.0.29-0.20220801143717-73008cfda2fb // indirect github.com/mattermost/morph v0.0.0-20220401091636-39f834798da8 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect @@ -51,6 +55,7 @@ require ( github.com/minio/minio-go/v7 v7.0.28 // indirect github.com/minio/sha256-simd v1.0.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -92,6 +97,8 @@ require ( golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/tools v0.1.11 // indirect + google.golang.org/genproto v0.0.0-20220614165028-45ed7f3ff16e // indirect + google.golang.org/grpc v1.47.0 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/ini.v1 v1.66.6 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect diff --git a/linux/go.sum b/linux/go.sum index c7fba8b2f..a7473cf09 100644 --- a/linux/go.sum +++ b/linux/go.sum @@ -1,5 +1,3 @@ -bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= -bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -21,18 +19,6 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb 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.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -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= @@ -46,597 +32,127 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k 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/spanner v1.28.0/go.mod h1:7m6mtQZn/hMbMfx62ct5EWrGND4DNqkXyrmBPRS+OJo= 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= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -code.sajari.com/docconv v1.2.0/go.mod h1:r8yfCP6OKbZ9Xkd87aBa4nfpk6ud/PoyLwex3n6cXSc= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= -gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= -github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= -github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= -github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= -github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/adal v0.9.16/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 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/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= -github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= -github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= -github.com/HdrHistogram/hdrhistogram-go v0.9.0/go.mod h1:nxrse8/Tzg2tg3DZcZjm6qEclQKK70g0KxO61gFFZD4= -github.com/JalfResi/justext v0.0.0-20170829062021-c0282dea7198/go.mod h1:0SURuH1rsE8aVWvutuMZghRNrNrYEUzibzJfhEYR8L0= -github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/squirrel v1.5.2 h1:UiOEi2ZX4RCSkpiNDQN5kro/XIBpSRk9iTqdIRPzUXE= github.com/Masterminds/squirrel v1.5.2/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= -github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= -github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= -github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= -github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= -github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= -github.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= -github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= -github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= -github.com/Microsoft/hcsshim v0.9.2/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= -github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= -github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/goquery v1.4.1/go.mod h1:T9ezsOHcCrDCgA8aF1Cqr3sSYbO/xgdy8/R/XiIMAhA= -github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= -github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/RoaringBitmap/roaring v0.9.4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA= -github.com/RoaringBitmap/roaring v1.2.1/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA= -github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= -github.com/advancedlogic/GoOse v0.0.0-20191112112754-e742535969c1/go.mod h1:f3HCSN1fBWjcpGtXyM119MJgeQl838v6so/PQOqvE1w= -github.com/advancedlogic/GoOse v0.0.0-20210820140952-9d5822d4a625/go.mod h1:f3HCSN1fBWjcpGtXyM119MJgeQl838v6so/PQOqvE1w= -github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 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/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= -github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= -github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= -github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= -github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= -github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY= -github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apache/arrow/go/arrow v0.0.0-20210818145353-234c94e4ce64/go.mod h1:2qMFB56yOP3KzkB3PbYZ4AlUFg3a88F67TIx5lB/WwY= -github.com/apache/arrow/go/arrow v0.0.0-20211013220434-5962184e7a30/go.mod h1:Q7yQnSMnLvcXlZ8RV+jwz/6y1rQTqbX6C82SndT52Zs= -github.com/araddon/dateparse v0.0.0-20180729174819-cfd92a431d0e/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI= -github.com/araddon/dateparse v0.0.0-20200409225146-d820a6159ab1/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI= -github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/avct/uasurfer v0.0.0-20191028135549-26b5daa857f1/go.mod h1:noBAuukeYOXa0aXGqxr24tADqkwDO2KRD15FsuaZ5a8= -github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= -github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.44.34/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go-v2 v1.8.0/go.mod h1:xEFuWz+3TYdlPRuo+CqATbeDWIWyaT5uAPwPaWtgse0= -github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2/config v1.6.0/go.mod h1:TNtBVmka80lRPk5+S9ZqVfFszOQAGJJ9KbT3EM3CHNU= -github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw= -github.com/aws/aws-sdk-go-v2/credentials v1.3.2/go.mod h1:PACKuTJdt6AlXvEq8rFI4eDmoqDFC5DpVKQbWysaDgM= -github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.4.0/go.mod h1:Mj/U8OpDbcVcoctrYwA2bak8k/HFPdcLzI/vaiXMwuM= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.4.0/go.mod h1:eHwXu2+uE/T6gpnYWwBwqoeqRf9IXyCcolyOWDRAErQ= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.5.4/go.mod h1:Ex7XQmbFmgFHrjUX6TN3mApKW5Hglyga+F7wZHTtYhA= -github.com/aws/aws-sdk-go-v2/internal/ini v1.2.0/go.mod h1:Q5jATQc+f1MfZp3PDMhn6ry18hGvE0i8yvbXoKbnZaE= -github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.2.2/go.mod h1:EASdTcM1lGhUe1/p4gkojHwlGJkeoRjjr1sRCzup3Is= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.3.0/go.mod h1:v8ygadNyATSm6elwJ/4gzJwcFhri9RqS8skgHKiwXPU= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.2.2/go.mod h1:NXmNI41bdEsJMrD0v9rUvbGCB5GwdBEpKvUvIY3vTFg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.5.2/go.mod h1:QuL2Ym8BkrLmN4lUofXYq6000/i5jPjosCNK//t6gak= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.7.2/go.mod h1:np7TMuJNT83O0oDOSF8i4dF3dvGqA6hPYYo6YYkzgRA= -github.com/aws/aws-sdk-go-v2/service/s3 v1.12.0/go.mod h1:6J++A5xpo7QDsIeSqPK4UHqMSyPOCopa+zKtqAMhqVQ= -github.com/aws/aws-sdk-go-v2/service/s3 v1.16.1/go.mod h1:CQe/KvWV1AqRc65KqeJjrLzr5X2ijnFTTVzJW0VBRCI= -github.com/aws/aws-sdk-go-v2/service/sso v1.3.2/go.mod h1:J21I6kF+d/6XHVk7kp/cx9YVD2TMD2TbLwtRGVcinXo= -github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk= -github.com/aws/aws-sdk-go-v2/service/sts v1.6.1/go.mod h1:hLZ/AnkIKHLuPGjEiyghNEdvJ2PP0MgOxcmv9EBJ4xs= -github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g= -github.com/aws/smithy-go v1.7.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= -github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= -github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= -github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 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 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 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/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= -github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= -github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/bits-and-blooms/bitset v1.2.2/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/blevesearch/bleve/v2 v2.3.2/go.mod h1:96+xE5pZUOsr3Y4vHzV1cBC837xZCpwLlX0hrrxnvIg= -github.com/blevesearch/bleve_index_api v1.0.1/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4= -github.com/blevesearch/bleve_index_api v1.0.2/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4= -github.com/blevesearch/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:9eJDeqxJ3E7WnLebQUlPD7ZjSce7AnDb9vjGmMCbD0A= -github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M= -github.com/blevesearch/goleveldb v1.0.1/go.mod h1:WrU8ltZbIp0wAoig/MHbrPCXSOLpe79nz5lv5nqfYrQ= -github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk= -github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+7LMvAB5IbSA= -github.com/blevesearch/mmap-go v1.0.3/go.mod h1:pYvKl/grLQrBxuaRYgoTssa4rVujYYeenDp++2E+yvs= -github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs= -github.com/blevesearch/scorch_segment_api/v2 v2.1.0/go.mod h1:uch7xyyO/Alxkuxa+CGs79vw0QY8BENSBjg6Mw5L5DE= -github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ= -github.com/blevesearch/snowball v0.6.1/go.mod h1:ZF0IBg5vgpeoUhnMza2v0A/z8m1cWPlwhke08LpNusg= -github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs= -github.com/blevesearch/upsidedown_store_api v1.0.1/go.mod h1:MQDVGpHZrpe3Uy26zJBf/a8h0FZY6xJbthIMm8myH2Q= -github.com/blevesearch/vellum v1.0.7/go.mod h1:doBZpmRhwTsASB4QdUZANlJvqVAUdUyX0ZK7QJCTeBE= -github.com/blevesearch/vellum v1.0.8/go.mod h1:+cpRi/tqq49xUYSQN2P7A5zNSNrS+MscLeeaZ3J46UA= -github.com/blevesearch/zapx/v11 v11.3.3/go.mod h1:YzTfUm4kS3e8OmTXDHVV8OzC5MWPO/VPJZQgPNVb4Lc= -github.com/blevesearch/zapx/v11 v11.3.4/go.mod h1:HJ7qdfBxdziuymKvXbsBVhCK5pB98tdzQbc8pJV6tJo= -github.com/blevesearch/zapx/v12 v12.3.3/go.mod h1:RMl6lOZqF+sTxKvhQDJ5yK2LT3Mu7E2p/jGdjAaiRxs= -github.com/blevesearch/zapx/v12 v12.3.4/go.mod h1:uQrKrK9XjXAAsJfAIE8ViLqIKP/keA2DQhS1XXpjkwA= -github.com/blevesearch/zapx/v13 v13.3.3/go.mod h1:eppobNM35U4C22yDvTuxV9xPqo10pwfP/jugL4INWG4= -github.com/blevesearch/zapx/v13 v13.3.4/go.mod h1:Wl7hO1gT+IDvJb7i06g2iW5Qvw0KzncJPsBx7WGWhLA= -github.com/blevesearch/zapx/v14 v14.3.3/go.mod h1:zXNcVzukh0AvG57oUtT1T0ndi09H0kELNaNmekEy0jw= -github.com/blevesearch/zapx/v14 v14.3.4/go.mod h1:b1YhRXXhAj9i+9aOwhRKCHUmJyYieK/QbDvPJDLddUk= -github.com/blevesearch/zapx/v15 v15.3.3/go.mod h1:C+f/97ZzTzK6vt/7sVlZdzZxKu+5+j4SrGCvr9dJzaY= -github.com/blevesearch/zapx/v15 v15.3.4/go.mod h1:TQ/qDC2q7TSSpeC6Vgr9fDN56Ra0u49lZJQ4v30WEx4= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= -github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= -github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= -github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= -github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= 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/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= 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 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= -github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= -github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= 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/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= -github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= -github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= -github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= 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/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/cockroachdb/cockroach-go/v2 v2.1.1/go.mod h1:7NtUnP6eK+l6k483WSYNrq3Kb23bWV10IRV1TyeSpwM= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= -github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= -github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= -github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= -github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= -github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= -github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= -github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= -github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= -github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= -github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= -github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= -github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= -github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= -github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= -github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= -github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= -github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= -github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= -github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= -github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= -github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= -github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= -github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= -github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= -github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= -github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= -github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= -github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= -github.com/containerd/containerd v1.6.1/go.mod h1:1nJz5xCZPusx6jJU8Frfct988y0NpumIq9ODB0kLtoE= -github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= -github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= -github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= -github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= -github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= -github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= -github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= -github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= -github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= -github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= -github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= -github.com/containerd/go-cni v1.1.0/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= -github.com/containerd/go-cni v1.1.3/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= -github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= -github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= -github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= -github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= -github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= -github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= -github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= -github.com/containerd/imgcrypt v1.1.3/go.mod h1:/TPA1GIDXMzbj01yd8pIbQiLdQxed5ue1wb8bP7PQu4= -github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= -github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM= -github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= -github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ= -github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= -github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= -github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= -github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= -github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= -github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= -github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y= -github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= -github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= -github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNBDZcxSOplJT5ico8/FLE= -github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= -github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= -github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= -github.com/containers/ocicrypt v1.1.2/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= -github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 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.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k= -github.com/couchbase/moss v0.2.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -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/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= -github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= -github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= -github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= -github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= -github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= -github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= github.com/dave/jennifer v1.4.1/go.mod h1:7jEdnm+qBcxl8PC0zyp7vxcpSRnzXSt9r39tpTVGlwA= 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/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= -github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= -github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/dgoogauth v0.0.0-20190221195224-5a805980a5f3/go.mod h1:hEfFauPHz7+NnjR/yHJGhrKo1Za+zStgwUETx3yzqgY= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/dhui/dktest v0.3.10/go.mod h1:h5Enh0nG3Qbo9WjNFRrwmKUaePEBhXMOygbz3Ww7Sz0= -github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= -github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= -github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v20.10.13+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= -github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= -github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= -github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dyatlov/go-opengraph v0.0.0-20210112100619-dae8665a5b09 h1:AQLr//nh20BzN3hIWj2+/Gt3FwSs8Nwo/nz4hMIcLPg= -github.com/dyatlov/go-opengraph v0.0.0-20210112100619-dae8665a5b09/go.mod h1:nYia/MIs9OyvXXYboPmNOj0gVWo97Wx0sde+ZuKkoM4= -github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a h1:etIrTD8BQqzColk9nKRusM9um5+1q0iOEJLqfBMIK64= +github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a/go.mod h1:emQhSYTXqB0xxjLITTw4EaWZ+8IIQYw+kx9GqNUKdLg= 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/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= -github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= -github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.5+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw= -github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= -github.com/gabriel-vasile/mimetype v1.3.1/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= -github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= -github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= -github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= -github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= -github.com/getsentry/sentry-go v0.13.0/go.mod h1:EOsfu5ZdvKPfeHYV6pTVQnsjfp30+XA7//UooKNumH0= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gigawattio/window v0.0.0-20180317192513-0f5467e35573/go.mod h1:eBvb3i++NHDH4Ugo9qCvMw8t0mTSctaEa5blJbWcNxs= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-asn1-ber/asn1-ber v1.3.2-0.20191121212151-29be175fc3a3/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= -github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= -github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= -github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= 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-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 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-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= 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-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= -github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= -github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= -github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-redis/redis/v8 v8.0.0/go.mod h1:isLoQT/NFSP7V67lyvM9GmdvLdyZ7pEhsXvvyQtnQTo= -github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= -github.com/go-resty/resty/v2 v2.0.0/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8= -github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU= -github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= -github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= -github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= -github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= -github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= -github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= -github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= -github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= -github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= -github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= -github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= -github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= -github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= -github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= -github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= -github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= -github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= -github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= -github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= -github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-migrate/migrate/v4 v4.15.2/go.mod h1:f2toGLkYqD3JH+Todi4aZ2ZdbeUNx4sIwiOK96rE9Lw= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 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/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= 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= @@ -645,10 +161,7 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt 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 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 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= @@ -664,21 +177,10 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD 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 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= -github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= 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/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 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= @@ -693,18 +195,12 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-github/v39 v39.2.0/go.mod h1:C1s8C5aCC9L+JXIYpJM5GYytdX52vC1bLvHEF1IhBrE= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.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= @@ -715,75 +211,39 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf 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-20201218002935-b9804c9f04c2/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-20210407192527-94a9f03dee38/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.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= 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/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= -github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= -github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= -github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= -github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/graph-gophers/dataloader/v6 v6.0.0/go.mod h1:J15OZSnOoZgMkijpbZcwCmglIDYqlUiTEE1xLPbyqZM= github.com/graph-gophers/graphql-go v1.4.0 h1:JE9wveRTSXwJyjdRd6bOQ7Ob5bewTUQ58Jv4OiVdpdE= github.com/graph-gophers/graphql-go v1.4.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c/go.mod h1:ObS/W+h8RYb1Y7fYivughjxojTmIu5iAIjSrSLCLeqE= -github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.2.1 h1:YQsLlGDJgwhXFpucSPyVbCBviQtjlHv3jLTlp8YmtEw= github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ= github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= @@ -791,106 +251,25 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX 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/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 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 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 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.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I= github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -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/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= -github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= -github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= -github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= -github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= -github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= -github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= -github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= -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/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.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk= -github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= -github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= -github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgerrcode v0.0.0-20201024163028-a0d42d470451/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= -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/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.0.7/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -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.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0= -github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po= -github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ= -github.com/jackc/pgtype v1.6.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig= -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.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA= -github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o= -github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= -github.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA= -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.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= -github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= -github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= -github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= -github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= -github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -900,202 +279,89 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= -github.com/k0kubun/pp v2.3.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= -github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= -github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= -github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= -github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= -github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= -github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= -github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= -github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -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.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY= github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.13 h1:1XxvOiqXZ8SULZUKim/wncr3wZ38H4yCuVDvKdK9OGs= github.com/klauspost/cpuid/v2 v2.0.13/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 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/konsorten/go-windows-terminal-sequences v1.0.3/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.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 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/krolaw/zipstream v0.0.0-20180621105154-0a2661891f94 h1:+AIlO01SKT9sfWU5CLWi0cfHc7dQwgGz3FhFRzXLoMg= github.com/krolaw/zipstream v0.0.0-20180621105154-0a2661891f94/go.mod h1:TcE3PIIkVWbP/HjhRAafgCjRKvDOi086iqp9VkNX/ng= -github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= -github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= -github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/levigross/exp-html v0.0.0-20120902181939-8df60c69a8f5/go.mod h1:QMe2wuKJ0o7zIVE8AqiT8rd8epmm6WDIZ2wyuBqYPzM= -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.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= -github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= -github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= -github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 h1:Khvh6waxG1cHc4Cz5ef9n3XVCxRWpAKUtqg9PJl5+y8= github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404/go.mod h1:RyS7FDNQlzF1PsjbJWHRI35exqaKGSO9qD4iv8QjE34= -github.com/mattermost/gziphandler v0.0.1/go.mod h1:CvvZR7sXqhj81V2swXuQY7T04Ccc89u7W7pHNPKev8g= github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d h1:/RJ/UV7M5c7L2TQ0KNm4yZxxFvC1nvRz/gY/Daa35aI= github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d/go.mod h1:HLbgMEI5K131jpxGazJ97AxfPDt31osq36YS1oxFQPQ= github.com/mattermost/logr/v2 v2.0.15 h1:+WNbGcsc3dBao65eXlceB6dTILNJRIrvubnsTl3zBew= github.com/mattermost/logr/v2 v2.0.15/go.mod h1:mpPp935r5dIkFDo2y9Q87cQWhFR/4xXpNh0k/y8Hmwg= -github.com/mattermost/mattermost-plugin-api v0.0.28-0.20220623051512-0afd85e854d4 h1:TF1yBBsLntuNb3wc3DRg30S9i6tv1JwtREtXd7Gnv9E= -github.com/mattermost/mattermost-plugin-api v0.0.28-0.20220623051512-0afd85e854d4/go.mod h1:jtiaM6selJi1Od1zGZDGO78hZyG0gI4/I2/8mza4OZg= -github.com/mattermost/mattermost-server/v6 v6.0.0-20220622145221-00016e3a4ff4/go.mod h1:e2CtTtnty6oH8CiHm40cMOqJ+dJeWEK39/tobCkeMAk= -github.com/mattermost/mattermost-server/v6 v6.0.0-20220711175838-7ee7523729e6 h1:lfkO5s/ZwuD2esAHGX+0EtmcsAVXJ0S5Xn37uWW/WoQ= -github.com/mattermost/mattermost-server/v6 v6.0.0-20220711175838-7ee7523729e6/go.mod h1:e2CtTtnty6oH8CiHm40cMOqJ+dJeWEK39/tobCkeMAk= +github.com/mattermost/mattermost-plugin-api v0.0.29-0.20220801143717-73008cfda2fb h1:q1qXKVv59rA2gcQ7lVLc5OlWBmfsR3i8mdGD5EZesyk= +github.com/mattermost/mattermost-plugin-api v0.0.29-0.20220801143717-73008cfda2fb/go.mod h1:PIeo40t9VTA4Wu1FwjzH7QmcgC3SRyk/ohCwJw4/oSo= +github.com/mattermost/mattermost-server/v6 v6.0.0-20220802151854-f07c31c5d933 h1:h7EibO8cwWeK8dLhC/A5tKGbkYSuJKZ0+2EXW7jDHoA= +github.com/mattermost/mattermost-server/v6 v6.0.0-20220802151854-f07c31c5d933/go.mod h1:otnBnKY9Y0eNkUKeD161de+BUBlESwANTnrkPT/392Y= github.com/mattermost/morph v0.0.0-20220401091636-39f834798da8 h1:gwliVjCTqAC01mSCNqa5nJ/4MmGq50vrjsottIhQ4d8= github.com/mattermost/morph v0.0.0-20220401091636-39f834798da8/go.mod h1:jxM3g1bx+k2Thz7jofcHguBS8TZn5Pc+o5MGmORObhw= -github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0/go.mod h1:nV5bfVpT//+B1RPD2JvRnxbkLmJEYXmRaaVl15fsXjs= -github.com/mattermost/squirrel v0.2.0/go.mod h1:NPPtk+CdpWre4GxMGoOpzEVFVc0ZoEFyJBZGCtn9nSU= 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.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -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.8/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.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/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.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= -github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= -github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= -github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= -github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= -github.com/microcosm-cc/bluemonday v1.0.18/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.0.28 h1:VMr3K5qGIEt+/KW3poopRh8mzi5RwuCjmrmstK196Fg= github.com/minio/minio-go/v7 v7.0.28/go.mod h1:x81+AX5gHSfCSqw7jxRKHvxUXMlE5uKX0Vb75Xk5yYg= -github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= -github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= -github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= -github.com/moby/sys/signal v0.6.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= -github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= -github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs= -github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= -github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -1103,238 +369,83 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= -github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= -github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mutecomm/go-sqlcipher/v4 v4.4.0/go.mod h1:PyN04SaWalavxRGH9E8ZftG6Ju7rsPrGmQRjrEaVpiY= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1:ncO5VaFWh0Nrt+4KT4mOZboaczBZcLuHrG+/sUeP8gI= -github.com/nicksnyder/go-i18n/v2 v2.0.3/go.mod h1:oDab7q8XCYMRlcrBnaY/7B1eOectbvj6B1UPBT+p5jo= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= -github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.0-20180506121414-d4647c9c7a84/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= -github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= -github.com/oov/psd v0.0.0-20220121172623-5db5eafcecbb/go.mod h1:GHI1bnmAcbp96z6LNfBJvtrjxhaXGkbsk967utPlvL8= -github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= -github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= -github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= -github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= -github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= -github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= -github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= -github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= -github.com/otiai10/gosseract/v2 v2.2.4/go.mod h1:ahOp/kHojnOMGv1RaUnR0jwY5JVa6BYKhYAS8nbMLSo= -github.com/otiai10/gosseract/v2 v2.3.1/go.mod h1:2ZOGgdTIXQzCS5f+N1HkcXRgDX6K3ZoYe3Yvo++cpp4= -github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= -github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= -github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= -github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pierrec/lz4/v4 v4.1.8/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= -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-0.20171018195549-f15c970de5b7/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/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 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/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 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 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.33.0 h1:rHgav/0a6+uYgGdNt3jwz8FNSesO/Hsang3O0T9A5SE= github.com/prometheus/common v0.33.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= -github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/reflog/dateconstraints v0.2.1/go.mod h1:Ax8AxTBcJc3E/oVS2hd2j7RDM/5MDtuPwuR7lIHtPLo= -github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/richardlehane/mscfb v1.0.3/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= -github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= -github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= -github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 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.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -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/rudderlabs/analytics-go v3.3.2+incompatible h1:bDajEJTYhfHjNYxbQFMA/2dHlOjyeSgxS7GPIdMZ52Q= github.com/rudderlabs/analytics-go v3.3.2+incompatible/go.mod h1:LF8/ty9kUX4PTY3l5c97K3nZZaX5Hwsvt+NBaRL/f30= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= -github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= -github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= -github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= -github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= -github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= -github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3 h1:ZuhckGJ10ulaKkdvJtiAqsLTiPrLaXSdnVgXJKJkTxE= github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= @@ -1344,7 +455,6 @@ github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9A github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= -github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= @@ -1354,73 +464,37 @@ github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1l github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= -github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= -github.com/simplereach/timeutils v1.2.0/go.mod h1:VVbQDfN/FHRZa1LSqcwo4kNZ62OOyqLLGQKYB3pB0Q8= -github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.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/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/snowflakedb/gosnowflake v1.6.3/go.mod h1:6hLajn6yxuJ4xUHZegMekpq9rnQbGJ7TMwXjgTmA6lg= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -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/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= -github.com/splitio/go-client/v6 v6.1.0/go.mod h1:CEGAEFT99Fwb32ZIRcnZoXTMXddtB6IIpTmt3RP8mnM= -github.com/splitio/go-split-commons/v3 v3.1.0/go.mod h1:29NCy20oAS4ZMy4qkwTd6277eieVDonx4V/aeDU/wUQ= -github.com/splitio/go-toolkit/v4 v4.2.0/go.mod h1:EdIHN0yzB1GTXDYQc0KdKvnjkO/jfUM2YqHVYfhD3Wo= -github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= -github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= -github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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= @@ -1432,54 +506,18 @@ github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8 github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= -github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= -github.com/throttled/throttled v2.2.5+incompatible/go.mod h1:0BjlrEGQmvxps+HuXLsyRdqpSRvJpq0PNIsOtqP9Nos= github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo= github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw= github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= -github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= -github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= @@ -1491,52 +529,16 @@ github.com/wiggin77/merror v1.0.3 h1:8+ZHV+aSnJoYghE3EUThl15C6rvF2TYRSvOSBjdmNR8 github.com/wiggin77/merror v1.0.3/go.mod h1:H2ETSu7/bPE0Ymf4bEwdUoo73OOEkdClnoRisfw0Nm0= github.com/wiggin77/srslog v1.0.1 h1:gA2XjSMy3DrRdX9UqLuDtuVAAshb8bE1NhX1YK0Qe+8= github.com/wiggin77/srslog v1.0.1/go.mod h1:fehkyYDq1QfuYn60TDPu9YdY2bB85VUW2mvN1WynEls= -github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= -github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= -github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= -github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g= github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM= -github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= -github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= 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/yuin/goldmark v1.4.12 h1:6hffw6vALvEDqJ19dOJvJKOoAOKe4NDaTqvd2sktGN0= github.com/yuin/goldmark v1.4.12/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= -github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= -go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= -go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= -go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= -go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= -go.mongodb.org/mongo-driver v1.7.0/go.mod h1:Q4oFMbo1+MSNqICAdYMlC/zSTrwCogR4R8NzkI+yfU8= -go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -1544,108 +546,39 @@ 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/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0/go.mod h1:vEhqr0m4eTc+DWxfsXoXue2GBgV2uUwVznkGIHW/e5w= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= -go.opentelemetry.io/otel v0.11.0/go.mod h1:G8UCk+KooF2HLkgo8RHX9epABH/aRGYET7gQOqBVdB0= -go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= -go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI= -go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1:QNX1aly8ehqqX1LEa6YniTU7VY9I6R3X/oPxhGdTceE= -go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= -go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= -go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= -go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= -go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= -go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= -go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= -go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= -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.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -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.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= -golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/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-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/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-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/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-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= 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/exp v0.0.0-20200908183739-ae8ad444f925/go.mod h1:1phAWC201xIgDyaFpmDeZkgf70Q4Pd/CNqfRtVPtxNw= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 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-20190910094157-69e4b8554b2a/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-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20220601225756-64ec528b34cd/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 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= @@ -1658,7 +591,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu 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= @@ -1667,46 +599,31 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB 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.3.1-0.20200828183125-ce943fd02449/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.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/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-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181108082009-03003ca0c849/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-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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-20190225153610-fe579d43d832/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-20190313220215-9f648a60d977/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-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 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-20190619014844-b5b0513f8c1b/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-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191112182307-2180aed22343/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= @@ -1717,47 +634,24 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ 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-20200520004742-59133d7f0dd7/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-20200602114024-627f9648deb9/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-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 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-20201202161906-c7110b5ffcbb/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-20201224014010-6772e930b67b/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-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220614195744-fb05da6f9022 h1:0qjDla5xICC2suMtyRH/QqX3B1btXTfNsIt/i4LFgO0= golang.org/x/net v0.0.0-20220614195744-fb05da6f9022/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/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= @@ -1767,30 +661,19 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ 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-20210402161424-2e8d93401602/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-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= 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-20190412183630-56d357773e84/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/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= @@ -1799,54 +682,26 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/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-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/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-20190316082340-a2f829d7f35f/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-20190419153524-e8e3143a4f4a/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-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/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-20200120151820-655fe14d7479/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-20200217220822-9197077df867/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= @@ -1854,82 +709,36 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w 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-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/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-20210324051608-47abb6519492/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-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/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-20210603081109-ebe580a85c40/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-20210616045830-e2b7044e8c71/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-20210809222454-d867a43fc93e/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-20210818153620-00dd8d7831e7/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-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/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-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/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-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098 h1:PgOr27OhUx2IRqGJ2RxAWI4dJQ7bi9cSrB82uzFzfUA= golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098/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-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1945,50 +754,25 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb 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/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181221001348-537d06c36207/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-20190206041539-40960b6deb8e/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-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190420181800-aa740d480789/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-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/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-20190624222133-a101b041ded4/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-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= 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-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/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-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/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= @@ -2009,49 +793,28 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs 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-20200505023115-26f46d2f7ef8/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-20200616133436-c1934b75d054/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-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/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-20201224043029-2b0845dc783e/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.0.0-20210108195828-e2f9c7f1fc8e/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.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY= golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= -gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= -google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= @@ -2074,20 +837,6 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 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.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -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.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= -google.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2097,8 +846,6 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww 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/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= -google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -2108,7 +855,6 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn 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-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= 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= @@ -2117,7 +863,6 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx 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-20200117163144-32f20d992d24/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= @@ -2126,69 +871,31 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG 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-20200423170343-7949de9c1215/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-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 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-20201019141844-1ed22bb0c154/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-20201110150050-8816d57aaa9a/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-20210108203827-ffc7fda8c3d7/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-20210226172003-ab064af71705/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-20210630183607-d20f26d13c79/go.mod h1:yiaVoXHpRzHGyxV3o4DktVWY4mSUErTKaeEOq6C3t3U= -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-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/genproto v0.0.0-20220111164026-67b88f271998/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= google.golang.org/genproto v0.0.0-20220614165028-45ed7f3ff16e h1:ubR4JUtqN3ffdFjpKylv8scWk/mZstGmzXbgYSkuMl0= google.golang.org/genproto v0.0.0-20220614165028-45ed7f3ff16e/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= 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.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 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.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= 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= @@ -2203,20 +910,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv 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 v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -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= @@ -2232,62 +927,31 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI= gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= -gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 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.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg= -gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= -gorm.io/gorm v1.21.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -2297,54 +961,9 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh 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= -k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= -k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= -k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= -k8s.io/api v0.22.5/go.mod h1:mEhXyLaSD1qTOf40rRiKXkc+2iCem09rWLlFwhCEiAs= -k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= -k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= -k8s.io/apimachinery v0.22.5/go.mod h1:xziclGKwuuJ2RM5/rSFQSYAj0zdbci3DH8kj+WvyN0U= -k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= -k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= -k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= -k8s.io/apiserver v0.22.5/go.mod h1:s2WbtgZAkTKt679sYtSudEQrTGWUSQAPe6MupLnlmaQ= -k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= -k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= -k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= -k8s.io/client-go v0.22.5/go.mod h1:cs6yf/61q2T1SdQL5Rdcjg9J1ElXSwbjSrW2vFImM4Y= -k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= -k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= -k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= -k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= -k8s.io/component-base v0.22.5/go.mod h1:VK3I+TjuF9eaa+Ln67dKxhGar5ynVbwnGrUiNF4MqCI= -k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= -k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= -k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= -k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= -k8s.io/cri-api v0.23.1/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= -k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg= -modernc.org/cc/v3 v3.32.4/go.mod h1:0R6jl1aZlIl2avnYfbfHBS1QB6/f+16mihBObaBC878= modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= @@ -2363,7 +982,6 @@ modernc.org/cc/v3 v3.35.20/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g modernc.org/cc/v3 v3.35.22/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/cc/v3 v3.35.24 h1:vlCqjhVwX15t1uwlMPpOpNRC7JTjMZ9lT9DYHKQTFuA= modernc.org/cc/v3 v3.35.24/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/ccgo/v3 v3.9.2/go.mod h1:gnJpy6NIVqkETT+L5zPsQFj7L2kkhfPMzOghRNv/CFo= modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60= modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw= modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI= @@ -2415,15 +1033,8 @@ modernc.org/ccgo/v3 v3.15.17/go.mod h1:bofnFkpRFf5gLY+mBZIyTW6FEcp26xi2lgOFk2Rlv modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= -modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8= -modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw= -modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8= -modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= -modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= -modernc.org/libc v1.9.5/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q= modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg= @@ -2475,8 +1086,6 @@ modernc.org/libc v1.14.10/go.mod h1:y1MtIWhwpJFpLYm6grAThtuXJKEsY6xkdZmXbRngIdo= modernc.org/libc v1.14.11/go.mod h1:l5/Mz/GrZwOqzwRHA3abgSCnSeJzzTl+Ify0bAwKbAw= modernc.org/libc v1.14.12 h1:pUBZTYoISfbb4pCf4PECENpbvwDBxeKc+/dS9LyOWFM= modernc.org/libc v1.14.12/go.mod h1:fJdoe23MHu2ruPQkFPPqCpToDi5cckzsbmkI6Ez0LqQ= -modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8= -modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= @@ -2489,39 +1098,21 @@ modernc.org/memory v1.0.7 h1:UE3cxTRFa5tfUibAV7Jqq8P7zRY0OlJg+yWVIIaluEE= modernc.org/memory v1.0.7/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY= -modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k= -modernc.org/sqlite v1.10.6/go.mod h1:Z9FEjUtZP4qFEg6/SiADg9XCER7aYy9a/j7Pg9P7CPs= modernc.org/sqlite v1.14.3/go.mod h1:xMpicS1i2MJ4C8+Ap0vYBqTwYfpFvdnPE6brbFOtV2Y= modernc.org/sqlite v1.15.3 h1:3C4AWicF7S5vUUFJuBi7Ws8eWlPjqyo/c4Z1UGYBbyg= modernc.org/sqlite v1.15.3/go.mod h1:J7GAPbk8Txp0DJnT8TGwpUqJW0Z1cK2YpzjoXaZRU8k= -modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= -modernc.org/tcl v1.5.2/go.mod h1:pmJYOLgpiys3oI4AeAafkcUfE+TKKilminxNyU/+Zlo= modernc.org/tcl v1.9.2/go.mod h1:aw7OnlIoiuJgu1gwbTZtrKnGpDqH9wyH++jZcxdqNsg= modernc.org/tcl v1.11.2 h1:mXpsx3AZqJt83uDiFu9UYQVBjNjaWKGCF1YDSlpCL6Y= modernc.org/tcl v1.11.2/go.mod h1:BRzgpajcGdS2qTxniOx9c/dcxjlbA7p12eJNmiriQYo= modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= -modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= modernc.org/z v1.2.20/go.mod h1:zU9FiF4PbHdOTUxw+IF8j7ArBMRPsHgq10uVPt6xTzo= modernc.org/z v1.3.2 h1:4GWBVMa48UDC7KQ9tnaggN/yTlXg+CdCX9bhgHPQ9AM= modernc.org/z v1.3.2/go.mod h1:PEU2oK2OEA1CfzDTd+8E908qEXhC9s0MfyKp5LZsd+k= -modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/mattermost-plugin/Makefile b/mattermost-plugin/Makefile index c44066e3d..4d883d09a 100644 --- a/mattermost-plugin/Makefile +++ b/mattermost-plugin/Makefile @@ -61,7 +61,7 @@ apply: ./build/bin/manifest apply setup-go-work: ## Sets up a go.work file - cd ..; go run ./mattermost-plugin/build/gowork/main.go + cd ..; go run ./build/gowork/main.go ## Runs eslint and golangci-lint .PHONY: check-style diff --git a/mattermost-plugin/build/go.mod b/mattermost-plugin/build/go.mod index 2d20c6199..679c3003e 100644 --- a/mattermost-plugin/build/go.mod +++ b/mattermost-plugin/build/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/go-git/go-git/v5 v5.1.0 - github.com/mattermost/mattermost-server/v6 v6.0.0-20220705131644-b99bd0d04915 + github.com/mattermost/mattermost-server/v6 v6.0.0-20220802151854-f07c31c5d933 github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.7.2 sigs.k8s.io/yaml v1.2.0 @@ -14,7 +14,7 @@ require ( github.com/blang/semver v3.5.1+incompatible // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.0 // indirect - github.com/dyatlov/go-opengraph v0.0.0-20210112100619-dae8665a5b09 // indirect + github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a // indirect github.com/emirpasic/gods v1.12.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect diff --git a/mattermost-plugin/build/go.sum b/mattermost-plugin/build/go.sum index f490e391e..c02b99197 100644 --- a/mattermost-plugin/build/go.sum +++ b/mattermost-plugin/build/go.sum @@ -28,8 +28,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dyatlov/go-opengraph v0.0.0-20210112100619-dae8665a5b09 h1:AQLr//nh20BzN3hIWj2+/Gt3FwSs8Nwo/nz4hMIcLPg= -github.com/dyatlov/go-opengraph v0.0.0-20210112100619-dae8665a5b09/go.mod h1:nYia/MIs9OyvXXYboPmNOj0gVWo97Wx0sde+ZuKkoM4= +github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a h1:etIrTD8BQqzColk9nKRusM9um5+1q0iOEJLqfBMIK64= +github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a/go.mod h1:emQhSYTXqB0xxjLITTw4EaWZ+8IIQYw+kx9GqNUKdLg= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= @@ -118,8 +118,8 @@ github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d h1:/RJ/UV7M5c7L2TQ github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d/go.mod h1:HLbgMEI5K131jpxGazJ97AxfPDt31osq36YS1oxFQPQ= github.com/mattermost/logr/v2 v2.0.15 h1:+WNbGcsc3dBao65eXlceB6dTILNJRIrvubnsTl3zBew= github.com/mattermost/logr/v2 v2.0.15/go.mod h1:mpPp935r5dIkFDo2y9Q87cQWhFR/4xXpNh0k/y8Hmwg= -github.com/mattermost/mattermost-server/v6 v6.0.0-20220705131644-b99bd0d04915 h1:W7y+l87t0qORLQMFXtz/s9rxftWZDop8Es6wYQLr5vk= -github.com/mattermost/mattermost-server/v6 v6.0.0-20220705131644-b99bd0d04915/go.mod h1:e2CtTtnty6oH8CiHm40cMOqJ+dJeWEK39/tobCkeMAk= +github.com/mattermost/mattermost-server/v6 v6.0.0-20220802151854-f07c31c5d933 h1:h7EibO8cwWeK8dLhC/A5tKGbkYSuJKZ0+2EXW7jDHoA= +github.com/mattermost/mattermost-server/v6 v6.0.0-20220802151854-f07c31c5d933/go.mod h1:otnBnKY9Y0eNkUKeD161de+BUBlESwANTnrkPT/392Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= @@ -247,6 +247,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220614195744-fb05da6f9022 h1:0qjDla5xICC2suMtyRH/QqX3B1btXTfNsIt/i4LFgO0= golang.org/x/net v0.0.0-20220614195744-fb05da6f9022/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -270,9 +271,12 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098 h1:PgOr27OhUx2IRqGJ2RxAWI4dJQ7bi9cSrB82uzFzfUA= golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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= diff --git a/mattermost-plugin/go.mod b/mattermost-plugin/go.mod index c6d6f8818..2856ebe70 100644 --- a/mattermost-plugin/go.mod +++ b/mattermost-plugin/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/golang/mock v1.6.0 - github.com/mattermost/mattermost-plugin-api v0.0.28-0.20220623051512-0afd85e854d4 + github.com/mattermost/mattermost-plugin-api v0.0.29-0.20220801143717-73008cfda2fb github.com/stretchr/testify v1.7.2 ) @@ -49,7 +49,7 @@ require ( github.com/disintegration/imaging v1.6.2 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/dustin/go-humanize v1.0.0 // indirect - github.com/dyatlov/go-opengraph v0.0.0-20210112100619-dae8665a5b09 // indirect + github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a // indirect github.com/fatih/color v1.13.0 // indirect github.com/fatih/set v0.2.1 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect @@ -96,7 +96,7 @@ require ( github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d // indirect github.com/mattermost/logr/v2 v2.0.15 // indirect - github.com/mattermost/mattermost-server/v6 v6.0.0-20220711175838-7ee7523729e6 // indirect + github.com/mattermost/mattermost-server/v6 v6.0.0-20220802151854-f07c31c5d933 // indirect github.com/mattermost/morph v0.0.0-20220401091636-39f834798da8 // indirect github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0 // indirect github.com/mattermost/squirrel v0.2.0 // indirect diff --git a/mattermost-plugin/go.sum b/mattermost-plugin/go.sum index a1a5fb5ea..8212b021f 100644 --- a/mattermost-plugin/go.sum +++ b/mattermost-plugin/go.sum @@ -582,6 +582,8 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn github.com/dvyukov/go-fuzz v0.0.0-20210429054444-fca39067bc72/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/dyatlov/go-opengraph v0.0.0-20210112100619-dae8665a5b09 h1:AQLr//nh20BzN3hIWj2+/Gt3FwSs8Nwo/nz4hMIcLPg= github.com/dyatlov/go-opengraph v0.0.0-20210112100619-dae8665a5b09/go.mod h1:nYia/MIs9OyvXXYboPmNOj0gVWo97Wx0sde+ZuKkoM4= +github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a h1:etIrTD8BQqzColk9nKRusM9um5+1q0iOEJLqfBMIK64= +github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a/go.mod h1:emQhSYTXqB0xxjLITTw4EaWZ+8IIQYw+kx9GqNUKdLg= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= @@ -1240,6 +1242,8 @@ github.com/mattermost/mattermost-plugin-api v0.0.27 h1:zFKQ6JW1/f0MfR5dP9P2umNNY github.com/mattermost/mattermost-plugin-api v0.0.27/go.mod h1:MM+tZ+36Obm9jqcveoxY2RFbwLaZKZUgR1zUlc0UBYw= github.com/mattermost/mattermost-plugin-api v0.0.28-0.20220623051512-0afd85e854d4 h1:TF1yBBsLntuNb3wc3DRg30S9i6tv1JwtREtXd7Gnv9E= github.com/mattermost/mattermost-plugin-api v0.0.28-0.20220623051512-0afd85e854d4/go.mod h1:jtiaM6selJi1Od1zGZDGO78hZyG0gI4/I2/8mza4OZg= +github.com/mattermost/mattermost-plugin-api v0.0.29-0.20220801143717-73008cfda2fb h1:q1qXKVv59rA2gcQ7lVLc5OlWBmfsR3i8mdGD5EZesyk= +github.com/mattermost/mattermost-plugin-api v0.0.29-0.20220801143717-73008cfda2fb/go.mod h1:PIeo40t9VTA4Wu1FwjzH7QmcgC3SRyk/ohCwJw4/oSo= github.com/mattermost/mattermost-server/v6 v6.0.0-20210901153517-42e75fad4dae/go.mod h1:kmxJuVgpX13Th+e5L1ZsBs4aq+ETmmDg9joo5r4cIw8= github.com/mattermost/mattermost-server/v6 v6.0.0-20220622145221-00016e3a4ff4/go.mod h1:e2CtTtnty6oH8CiHm40cMOqJ+dJeWEK39/tobCkeMAk= github.com/mattermost/mattermost-server/v6 v6.0.0-20220630162014-b45ff0be5d61 h1:e0HqxbOXRsm4UZ7HqZ4tagzEvlqlyfJcYp0vZ+Jg2dE= @@ -1248,6 +1252,8 @@ github.com/mattermost/mattermost-server/v6 v6.0.0-20220705131644-b99bd0d04915 h1 github.com/mattermost/mattermost-server/v6 v6.0.0-20220705131644-b99bd0d04915/go.mod h1:e2CtTtnty6oH8CiHm40cMOqJ+dJeWEK39/tobCkeMAk= github.com/mattermost/mattermost-server/v6 v6.0.0-20220711175838-7ee7523729e6 h1:lfkO5s/ZwuD2esAHGX+0EtmcsAVXJ0S5Xn37uWW/WoQ= github.com/mattermost/mattermost-server/v6 v6.0.0-20220711175838-7ee7523729e6/go.mod h1:e2CtTtnty6oH8CiHm40cMOqJ+dJeWEK39/tobCkeMAk= +github.com/mattermost/mattermost-server/v6 v6.0.0-20220802151854-f07c31c5d933 h1:h7EibO8cwWeK8dLhC/A5tKGbkYSuJKZ0+2EXW7jDHoA= +github.com/mattermost/mattermost-server/v6 v6.0.0-20220802151854-f07c31c5d933/go.mod h1:otnBnKY9Y0eNkUKeD161de+BUBlESwANTnrkPT/392Y= github.com/mattermost/mattermost-server/v6 v6.3.0/go.mod h1:L9gIoi9ESBh/NefsaZCfOVBMnbhx+v3kXhInGt3DQmA= github.com/mattermost/mattermost-server/v6 v6.7.2 h1:rRss2/R5LNbyc/P1OA4kSWuVq+rmnxwepuwGpTwL+U4= github.com/mattermost/mattermost-server/v6 v6.7.2/go.mod h1:b/iDf7Jn2Pd2jWGzaznoVNT811JZpemdmNGP7M/a7Ao= @@ -2148,6 +2154,7 @@ golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220614195744-fb05da6f9022 h1:0qjDla5xICC2suMtyRH/QqX3B1btXTfNsIt/i4LFgO0= golang.org/x/net v0.0.0-20220614195744-fb05da6f9022/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= diff --git a/mattermost-plugin/plugin.json b/mattermost-plugin/plugin.json index f3d13c25a..de38afec6 100644 --- a/mattermost-plugin/plugin.json +++ b/mattermost-plugin/plugin.json @@ -7,7 +7,7 @@ "release_notes_url": "https://github.com/mattermost/focalboard/releases", "icon_path": "assets/starter-template-icon.svg", "version": "7.3.0", - "min_server_version": "7.0.0", + "min_server_version": "7.2.0", "server": { "executables": { "linux-amd64": "server/dist/plugin-linux-amd64", diff --git a/mattermost-plugin/product/api_adapter.go b/mattermost-plugin/product/api_adapter.go index 75dcbd577..e18d0a7fb 100644 --- a/mattermost-plugin/product/api_adapter.go +++ b/mattermost-plugin/product/api_adapter.go @@ -6,6 +6,8 @@ package product import ( "database/sql" + "github.com/gorilla/mux" + "github.com/mattermost/mattermost-server/v6/app/request" mm_model "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/shared/mlog" @@ -34,7 +36,7 @@ type serviceAPIAdapter struct { func newServiceAPIAdapter(api *boardsProduct) *serviceAPIAdapter { return &serviceAPIAdapter{ api: api, - ctx: &request.Context{}, + ctx: request.EmptyContext(api.logger), } } @@ -94,7 +96,7 @@ func (a *serviceAPIAdapter) GetUserByEmail(email string) (*mm_model.User, error) } func (a *serviceAPIAdapter) UpdateUser(user *mm_model.User) (*mm_model.User, error) { - user, appErr := a.api.userService.UpdateUser(user, true) + user, appErr := a.api.userService.UpdateUser(a.ctx, user, true) return user, normalizeAppErr(appErr) } @@ -205,5 +207,12 @@ func (a *serviceAPIAdapter) GetDiagnosticID() string { return a.api.systemService.GetDiagnosticId() } +// +// Router service. +// +func (a *serviceAPIAdapter) RegisterRouter(sub *mux.Router) { + a.api.routerService.RegisterRouter(boardsProductName, sub) +} + // Ensure the adapter implements ServicesAPI. var _ model.ServicesAPI = &serviceAPIAdapter{} diff --git a/mattermost-plugin/product/boards_product.go b/mattermost-plugin/product/boards_product.go index 7b6e15a86..c2e62ed92 100644 --- a/mattermost-plugin/product/boards_product.go +++ b/mattermost-plugin/product/boards_product.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/mattermost/focalboard/mattermost-plugin/server/boards" + "github.com/mattermost/focalboard/server/model" "github.com/mattermost/mattermost-server/v6/app" mm_model "github.com/mattermost/mattermost-server/v6/model" @@ -197,6 +198,8 @@ func (bp *boardsProduct) Start() error { return fmt.Errorf("failed to create Boards service: %w", err) } + model.LogServerInfo(bp.logger) + bp.boardsApp = boardsApp if err := bp.boardsApp.Start(); err != nil { return fmt.Errorf("failed to start Boards service: %w", err) diff --git a/mattermost-plugin/server/api_adapter.go b/mattermost-plugin/server/api_adapter.go index 6100d5489..e5ce64ccc 100644 --- a/mattermost-plugin/server/api_adapter.go +++ b/mattermost-plugin/server/api_adapter.go @@ -6,6 +6,7 @@ package main import ( "database/sql" + "github.com/gorilla/mux" "github.com/mattermost/focalboard/server/model" "github.com/mattermost/mattermost-server/v6/plugin" @@ -208,5 +209,12 @@ func (a *pluginAPIAdapter) GetDiagnosticID() string { return a.api.GetDiagnosticId() } +// +// Router service. +// +func (a *pluginAPIAdapter) RegisterRouter(sub *mux.Router) { + // NOOP for plugin +} + // Ensure the adapter implements ServicesAPI. var _ model.ServicesAPI = &pluginAPIAdapter{} diff --git a/mattermost-plugin/server/boards/boardsapp.go b/mattermost-plugin/server/boards/boardsapp.go index 0abb105a9..bbcc3660a 100644 --- a/mattermost-plugin/server/boards/boardsapp.go +++ b/mattermost-plugin/server/boards/boardsapp.go @@ -170,6 +170,11 @@ func (b *BoardsApp) Start() error { if err := b.server.Start(); err != nil { return fmt.Errorf("error starting Boards server: %w", err) } + + b.servicesAPI.RegisterRouter(b.server.GetRootRouter()) + + b.logger.Info("Boards product successfully started.") + return nil } diff --git a/mattermost-plugin/server/boards/notifications.go b/mattermost-plugin/server/boards/notifications.go index 1e093f8d8..62ab31735 100644 --- a/mattermost-plugin/server/boards/notifications.go +++ b/mattermost-plugin/server/boards/notifications.go @@ -75,6 +75,7 @@ func createDelivery(servicesAPI model.ServicesAPI, serverRoot string) (*pluginde Username: botUsername, DisplayName: botDisplayname, Description: botDescription, + OwnerId: model.SystemUserID, } botID, err := servicesAPI.EnsureBot(bot) if err != nil { diff --git a/mattermost-plugin/server/manifest.go b/mattermost-plugin/server/manifest.go index 74863c7d6..74772482c 100644 --- a/mattermost-plugin/server/manifest.go +++ b/mattermost-plugin/server/manifest.go @@ -21,7 +21,7 @@ const manifestStr = ` "release_notes_url": "https://github.com/mattermost/focalboard/releases", "icon_path": "assets/starter-template-icon.svg", "version": "7.3.0", - "min_server_version": "7.0.0", + "min_server_version": "7.2.0", "server": { "executables": { "darwin-amd64": "server/dist/plugin-darwin-amd64", diff --git a/mattermost-plugin/server/plugin.go b/mattermost-plugin/server/plugin.go index a90807d5d..5b4ee2cd3 100644 --- a/mattermost-plugin/server/plugin.go +++ b/mattermost-plugin/server/plugin.go @@ -9,6 +9,7 @@ import ( "net/http" "github.com/mattermost/focalboard/mattermost-plugin/server/boards" + "github.com/mattermost/focalboard/server/model" pluginapi "github.com/mattermost/mattermost-plugin-api" @@ -52,6 +53,8 @@ func (p *Plugin) OnActivate() error { return fmt.Errorf("cannot activate plugin: %w", err) } + model.LogServerInfo(logger) + p.boardsApp = boardsApp return p.boardsApp.Start() } diff --git a/mattermost-plugin/webapp/package.json b/mattermost-plugin/webapp/package.json index 3b4a3d2f7..032f270bd 100644 --- a/mattermost-plugin/webapp/package.json +++ b/mattermost-plugin/webapp/package.json @@ -107,7 +107,7 @@ "text-summary" ], "moduleNameMapper": { - "^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "identity-obj-proxy", + "^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/../../webapp/__mocks__/fileMock.js", "^.+\\.(scss|css)$": "/tests/style_mock.json", "^.*i18n.*\\.(json)$": "/tests/i18n_mock.json", "^bundle-loader\\?lazy\\!(.*)$": "$1", diff --git a/mattermost-plugin/webapp/src/components/__snapshots__/boardSelector.test.tsx.snap b/mattermost-plugin/webapp/src/components/__snapshots__/boardSelector.test.tsx.snap index 7c294bcef..e81581852 100644 --- a/mattermost-plugin/webapp/src/components/__snapshots__/boardSelector.test.tsx.snap +++ b/mattermost-plugin/webapp/src/components/__snapshots__/boardSelector.test.tsx.snap @@ -181,20 +181,28 @@ exports[`components/boardSelector renders with some results 1`] = `
-
-
- Untitled board -
+ +
+ class="resultLine" + > +
+ Untitled board +
+
+
-
-
- Untitled board -
+ +
+ class="resultLine" + > +
+ Untitled board +
+
+
-
-
- Untitled board -
+ +
+ class="resultLine" + > +
+ Untitled board +
+
+
-
-
- Untitled board -
+ +
+ class="resultLine" + > +
+ Untitled board +
+
+
-
-
- Test title -
+ +
+ class="resultLine" + > +
+ Test title +
+
+
-
-
- Test title -
+ +
+ class="resultLine" + > +
+ Test title +
+
+
{ }) it('renders with no results', async () => { - mockedOctoClient.search.mockResolvedValueOnce([]) + mockedOctoClient.searchLinkableBoards.mockResolvedValueOnce([]) const store = mockStateStore([], state) const {container} = render(wrapIntl( @@ -74,7 +74,7 @@ describe('components/boardSelector', () => { }) it('renders with some results', async () => { - mockedOctoClient.search.mockResolvedValueOnce([createBoard(), createBoard(), createBoard()]) + mockedOctoClient.searchLinkableBoards.mockResolvedValueOnce([createBoard(), createBoard(), createBoard()]) const store = mockStateStore([], state) const {container} = render(wrapIntl( diff --git a/mattermost-plugin/webapp/src/components/boardSelector.tsx b/mattermost-plugin/webapp/src/components/boardSelector.tsx index 877f235dc..58839f144 100644 --- a/mattermost-plugin/webapp/src/components/boardSelector.tsx +++ b/mattermost-plugin/webapp/src/components/boardSelector.tsx @@ -12,8 +12,7 @@ import {useWebsockets} from '../../../../webapp/src/hooks/websockets' import octoClient from '../../../../webapp/src/octoClient' import mutator from '../../../../webapp/src/mutator' import {getCurrentTeamId, getAllTeams, Team} from '../../../../webapp/src/store/teams' -import {createBoard, BoardsAndBlocks, Board} from '../../../../webapp/src/blocks/board' -import {createBoardView} from '../../../../webapp/src/blocks/boardView' +import {createBoard, Board} from '../../../../webapp/src/blocks/board' import {useAppSelector, useAppDispatch} from '../../../../webapp/src/store/hooks' import {EmptySearch, EmptyResults} from '../../../../webapp/src/components/searchDialog/searchDialog' import ConfirmationDialog from '../../../../webapp/src/components/confirmationDialogBox' @@ -21,11 +20,13 @@ import Dialog from '../../../../webapp/src/components/dialog' import SearchIcon from '../../../../webapp/src/widgets/icons/search' import Button from '../../../../webapp/src/widgets/buttons/button' import {getCurrentLinkToChannel, setLinkToChannel} from '../../../../webapp/src/store/boards' -import TelemetryClient, {TelemetryCategory, TelemetryActions} from '../../../../webapp/src/telemetry/telemetryClient' import {WSClient} from '../../../../webapp/src/wsclient' +import {SuiteWindow} from '../../../../webapp/src/types/index' import BoardSelectorItem from './boardSelectorItem' +const windowAny = (window as SuiteWindow) + import './boardSelector.scss' const BoardSelector = () => { @@ -49,7 +50,7 @@ const BoardSelector = () => { if (query.trim().length === 0 || !teamId) { return } - const items = await octoClient.search(teamId, query) + const items = await octoClient.searchLinkableBoards(teamId, query) setResults(items) setIsSearching(false) @@ -107,27 +108,21 @@ const BoardSelector = () => { } const newLinkedBoard = async (): Promise => { - const board = {...createBoard(), teamId, channelId: currentChannel} + window.open(`${windowAny.frontendBaseURL}/team/${teamId}/new/${currentChannel}`, '_blank', 'noopener') + dispatch(setLinkToChannel('')) + } - const view = createBoardView() - view.fields.viewType = 'board' - view.parentId = board.id - view.boardId = board.id - view.title = intl.formatMessage({id: 'View.NewBoardTitle', defaultMessage: 'Board view'}) - - await mutator.createBoardsAndBlocks( - {boards: [board], blocks: [view]}, - 'add linked board', - async (bab: BoardsAndBlocks): Promise => { - const windowAny: any = window - const newBoard = bab.boards[0] - // TODO: Maybe create a new event for create linked board - TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.CreateBoard, {board: newBoard?.id}) - windowAny.WebappUtils.browserHistory.push(`/boards/team/${teamId}/${newBoard.id}`) - dispatch(setLinkToChannel('')) - }, - async () => {return}, - ) + let confirmationSubText + if (showLinkBoardConfirmation?.channelId !== '') { + confirmationSubText = intl.formatMessage({ + id: 'boardSelector.confirm-link-board-subtext-with-other-channel', + defaultMessage: 'When you link "{boardName}" to the channel, all members of the channel (existing and new) will be able to edit it.{lineBreak} This board is currently linked to another channel. It will be unlinked if you choose to link it here.' + }, {boardName: showLinkBoardConfirmation?.title, lineBreak:

}) + } else { + confirmationSubText = intl.formatMessage({ + id: 'boardSelector.confirm-link-board-subtext', + defaultMessage: 'When you link "{boardName}" to the channel, all members of the channel (existing and new) will be able to edit it. You can unlink a board from a channel at any time.' + }, {boardName: showLinkBoardConfirmation?.title}) } return ( @@ -146,11 +141,9 @@ const BoardSelector = () => { linkBoard(showLinkBoardConfirmation, true), onClose: () => setShowLinkBoardConfirmation(null), }} @@ -168,7 +161,7 @@ const BoardSelector = () => { onClick={() => newLinkedBoard()} emphasis='secondary' > - diff --git a/mattermost-plugin/webapp/src/components/boardSelectorItem.scss b/mattermost-plugin/webapp/src/components/boardSelectorItem.scss index 6aa64c87b..ecdf44892 100644 --- a/mattermost-plugin/webapp/src/components/boardSelectorItem.scss +++ b/mattermost-plugin/webapp/src/components/boardSelectorItem.scss @@ -5,9 +5,24 @@ padding: 10px 0; margin: 0 35px; + .BoardSelectorItem-info { + display: flex; + flex: 1; + overflow: hidden; + } + .icon { + width: 18px; align-items: flex-start; margin-right: 10px; + + i { + font-size: 20px; + } + + &:empty { + display: none; + } } .resultLine { @@ -34,5 +49,6 @@ display: flex; align-self: center; align-items: center; + padding-left: 16px; } } diff --git a/mattermost-plugin/webapp/src/components/boardSelectorItem.tsx b/mattermost-plugin/webapp/src/components/boardSelectorItem.tsx index 5da6f2241..3eff5bb94 100644 --- a/mattermost-plugin/webapp/src/components/boardSelectorItem.tsx +++ b/mattermost-plugin/webapp/src/components/boardSelectorItem.tsx @@ -6,6 +6,7 @@ import {useIntl, FormattedMessage} from 'react-intl' import {Board} from '../../../../webapp/src/blocks/board' import Button from '../../../../webapp/src/widgets/buttons/button' +import CompassIcon from '../../../../webapp/src/widgets/icons/compassIcon' import './boardSelectorItem.scss' @@ -23,10 +24,12 @@ const BoardSelectorItem = (props: Props) => { const resultTitle = item.title || untitledBoardTitle return (

- {item.icon} -
-
{resultTitle}
-
{item.description}
+
+ {item.icon || } +
+
{resultTitle}
+
{item.description}
+
{item.channelId === currentChannel && @@ -44,7 +47,7 @@ const BoardSelectorItem = (props: Props) => { onClick={() => props.linkBoard(item)} emphasis='primary' > - diff --git a/mattermost-plugin/webapp/src/components/rhsChannelBoardItem.tsx b/mattermost-plugin/webapp/src/components/rhsChannelBoardItem.tsx index ba1a58c70..542fe3a38 100644 --- a/mattermost-plugin/webapp/src/components/rhsChannelBoardItem.tsx +++ b/mattermost-plugin/webapp/src/components/rhsChannelBoardItem.tsx @@ -13,9 +13,12 @@ import OptionsIcon from '../../../../webapp/src/widgets/icons/options' import DeleteIcon from '../../../../webapp/src/widgets/icons/delete' import Menu from '../../../../webapp/src/widgets/menu' import MenuWrapper from '../../../../webapp/src/widgets/menuWrapper' +import {SuiteWindow} from '../../../../webapp/src/types/index' import './rhsChannelBoardItem.scss' +const windowAny = (window as SuiteWindow) + type Props = { board: Board } @@ -30,8 +33,7 @@ const RHSChannelBoardItem = (props: Props) => { } const handleBoardClicked = (boardID: string) => { - const windowAny: any = window - windowAny.WebappUtils.browserHistory.push(`/boards/team/${team.id}/${boardID}`) + window.open(`${windowAny.frontendBaseURL}/team/${team.id}/${boardID}`, '_blank', 'noopener') } const onUnlinkBoard = async (board: Board) => { diff --git a/mattermost-plugin/webapp/src/components/rhsChannelBoards.test.tsx b/mattermost-plugin/webapp/src/components/rhsChannelBoards.test.tsx index 3a9362021..652ab719e 100644 --- a/mattermost-plugin/webapp/src/components/rhsChannelBoards.test.tsx +++ b/mattermost-plugin/webapp/src/components/rhsChannelBoards.test.tsx @@ -37,6 +37,11 @@ describe('components/rhsChannelBoards', () => { current: team, currentId: team.id, }, + users: { + me: { + id: 'user-id', + }, + }, language: { value: 'en', }, diff --git a/mattermost-plugin/webapp/src/components/rhsChannelBoards.tsx b/mattermost-plugin/webapp/src/components/rhsChannelBoards.tsx index 10651cc95..ba3236252 100644 --- a/mattermost-plugin/webapp/src/components/rhsChannelBoards.tsx +++ b/mattermost-plugin/webapp/src/components/rhsChannelBoards.tsx @@ -10,9 +10,17 @@ import {useWebsockets} from '../../../../webapp/src/hooks/websockets' import {Board, BoardMember} from '../../../../webapp/src/blocks/board' import {getCurrentTeamId} from '../../../../webapp/src/store/teams' +import {IUser} from '../../../../webapp/src/user' +import {getMe, fetchMe} from '../../../../webapp/src/store/users' import {loadBoards} from '../../../../webapp/src/store/initialLoad' import {getCurrentChannel} from '../../../../webapp/src/store/channels' -import {getMySortedBoards, setLinkToChannel, updateBoards, updateMembers} from '../../../../webapp/src/store/boards' +import { + getMySortedBoards, + setLinkToChannel, + updateBoards, + updateMembersEnsuringBoardsAndUsers, + addMyBoardMemberships, +} from '../../../../webapp/src/store/boards' import {useAppSelector, useAppDispatch} from '../../../../webapp/src/store/hooks' import AddIcon from '../../../../webapp/src/widgets/icons/add' import Button from '../../../../webapp/src/widgets/buttons/button' @@ -29,11 +37,13 @@ const RHSChannelBoards = () => { const boards = useAppSelector(getMySortedBoards) const teamId = useAppSelector(getCurrentTeamId) const currentChannel = useAppSelector(getCurrentChannel) + const me = useAppSelector(getMe) const dispatch = useAppDispatch() const intl = useIntl() useEffect(() => { dispatch(loadBoards()) + dispatch(fetchMe()) }, []) useWebsockets(teamId || '', (wsClient: WSClient) => { @@ -41,7 +51,12 @@ const RHSChannelBoards = () => { dispatch(updateBoards(boards)) } const onChangeMemberHandler = (_: WSClient, members: BoardMember[]): void => { - dispatch(updateMembers(members)) + dispatch(updateMembersEnsuringBoardsAndUsers(members)) + + if (me) { + const myBoardMemberships = members.filter((boardMember) => boardMember.userId === me.id) + dispatch(addMyBoardMemberships(myBoardMemberships)) + } } wsClient.addOnChange(onChangeBoardHandler, 'board') @@ -51,7 +66,7 @@ const RHSChannelBoards = () => { wsClient.removeOnChange(onChangeBoardHandler, 'board') wsClient.removeOnChange(onChangeMemberHandler, 'boardMembers') } - }, []) + }, [me]) if (!boards) { return null diff --git a/mattermost-plugin/webapp/src/index.tsx b/mattermost-plugin/webapp/src/index.tsx index a0aed794b..72558f15c 100644 --- a/mattermost-plugin/webapp/src/index.tsx +++ b/mattermost-plugin/webapp/src/index.tsx @@ -1,6 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import React, {useEffect} from 'react' +import {createIntl, createIntlCache} from 'react-intl' import {Store, Action} from 'redux' import {Provider as ReduxProvider} from 'react-redux' import {createBrowserHistory, History} from 'history' @@ -13,6 +14,7 @@ import {selectTeam} from 'mattermost-redux/actions/teams' import {SuiteWindow} from '../../../webapp/src/types/index' import {UserSettings} from '../../../webapp/src/userSettings' +import {getMessages, getCurrentLanguage} from '../../../webapp/src/i18n' const windowAny = (window as SuiteWindow) @@ -37,6 +39,7 @@ import '../../../webapp/src/styles/focalboard-variables.scss' import '../../../webapp/src/styles/main.scss' import '../../../webapp/src/styles/labels.scss' import octoClient from '../../../webapp/src/octoClient' +import {Constants} from '../../../webapp/src/constants' import BoardsUnfurl from './components/boardsUnfurl/boardsUnfurl' import RHSChannelBoards from './components/rhsChannelBoards' @@ -182,6 +185,13 @@ export default class Plugin { windowAny.frontendBaseURL = subpath + windowAny.frontendBaseURL windowAny.baseURL = subpath + windowAny.baseURL browserHistory = customHistory() + const cache = createIntlCache() + const intl = createIntl({ + // modeled after in webapp/src/app.tsx + locale: getCurrentLanguage(), + messages: getMessages(getCurrentLanguage()) + }, cache) + this.registry = registry @@ -240,7 +250,9 @@ export default class Plugin { const currentTeamID = mmStore.getState().entities.teams.currentTeamId if (currentTeamID && currentTeamID !== prevTeamID) { if (prevTeamID && window.location.pathname.startsWith(windowAny.frontendBaseURL || '')) { - browserHistory.push(`/team/${currentTeamID}`) + // Don't re-push the URL if we're already on a URL for the current team + if (!window.location.pathname.startsWith(`${(windowAny.frontendBaseURL || '')}/team/${currentTeamID}`)) + browserHistory.push(`/team/${currentTeamID}`) } prevTeamID = currentTeamID store.dispatch(setTeam(currentTeamID)) @@ -249,6 +261,19 @@ export default class Plugin { } }) + let fbPrevTeamID = store.getState().teams.currentId + store.subscribe(() => { + const currentTeamID: string = store.getState().teams.currentId + const currentUserId = mmStore.getState().entities.users.currentUserId + if (currentTeamID !== fbPrevTeamID) { + fbPrevTeamID = currentTeamID + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + mmStore.dispatch(selectTeam(currentTeamID)) + localStorage.setItem(`user_prev_team:${currentUserId}`, currentTeamID) + } + }) + if (this.registry.registerProduct) { windowAny.frontendBaseURL = subpath + '/boards' @@ -284,8 +309,9 @@ export default class Plugin { const goToFocalboardTemplate = () => { const currentTeam = mmStore.getState().entities.teams.currentTeamId + const currentChannel = mmStore.getState().entities.channels.currentChannelId TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ClickChannelIntro, {teamID: currentTeam}) - window.open(`${windowAny.frontendBaseURL}/team/${currentTeam}`, '_blank', 'noopener') + window.open(`${windowAny.frontendBaseURL}/team/${currentTeam}/new/${currentChannel}`, '_blank', 'noopener') } if (registry.registerChannelIntroButtonAction) { @@ -294,7 +320,7 @@ export default class Plugin { if (this.registry.registerAppBarComponent) { const appBarIconURL = windowAny.baseURL + '/public/app-bar-icon.png' - this.registry.registerAppBarComponent(appBarIconURL, () => mmStore.dispatch(toggleRHSPlugin), 'Boards') + this.registry.registerAppBarComponent(appBarIconURL, () => mmStore.dispatch(toggleRHSPlugin), intl.formatMessage({id: 'AppBar.Tooltip', defaultMessage: 'Toggle Linked Boards'})) } this.registry.registerPostWillRenderEmbedComponent( @@ -309,6 +335,21 @@ export default class Plugin { ), false ) + + // Insights handler + if (this.registry?.registerInsightsHandler) { + this.registry?.registerInsightsHandler(async (timeRange: string, page: number, perPage: number, teamId: string, insightType: string) => { + if (insightType === Constants.myInsights) { + const data = await octoClient.getMyTopBoards(timeRange, page, perPage, teamId) + + return data + } + + const data = await octoClient.getTeamTopBoards(timeRange, page, perPage, teamId) + + return data + }) + } } this.boardSelectorId = this.registry.registerRootComponent((props: {webSocketClient: MMWebSocketClient}) => ( @@ -354,11 +395,6 @@ export default class Plugin { } } - windowAny.setTeamInSidebar = (teamID: string) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - mmStore.dispatch(selectTeam(teamID)) - } windowAny.getCurrentTeamId = (): string => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/mattermost-plugin/webapp/src/types/mattermost-webapp/index.d.ts b/mattermost-plugin/webapp/src/types/mattermost-webapp/index.d.ts index 6e6fd24fa..73b4082cb 100644 --- a/mattermost-plugin/webapp/src/types/mattermost-webapp/index.d.ts +++ b/mattermost-plugin/webapp/src/types/mattermost-webapp/index.d.ts @@ -17,6 +17,7 @@ export interface PluginRegistry { registerAppBarComponent(iconURL: string, action: (channel: Channel, member: ChannelMembership) => void, tooltipText: React.ReactNode) registerRightHandSidebarComponent(component: React.ElementType, title: React.Element) registerRootComponent(component: React.ElementType) + registerInsightsHandler(handler: (timeRange: string, page: number, perPage: number, teamId: string, insightType: string) => void) // Add more if needed from https://developers.mattermost.com/extend/plugins/webapp/reference } diff --git a/pull_request_template.md b/pull_request_template.md new file mode 100644 index 000000000..713d3ea44 --- /dev/null +++ b/pull_request_template.md @@ -0,0 +1,22 @@ + + +#### Summary + + +#### Ticket Link + diff --git a/server/api/api.go b/server/api/api.go index 380a6d7bb..1257aaad0 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -4,21 +4,14 @@ import ( "encoding/json" "errors" "fmt" - "io" - "io/ioutil" "net/http" - "path/filepath" "runtime/debug" - "strconv" - "strings" - "time" "github.com/gorilla/mux" "github.com/mattermost/focalboard/server/app" "github.com/mattermost/focalboard/server/model" "github.com/mattermost/focalboard/server/services/audit" "github.com/mattermost/focalboard/server/services/permissions" - "github.com/mattermost/focalboard/server/utils" "github.com/mattermost/mattermost-server/v6/shared/mlog" ) @@ -84,100 +77,28 @@ func (a *API) RegisterRoutes(r *mux.Router) { apiv2.Use(a.panicHandler) apiv2.Use(a.requireCSRFToken) - // personal-server specific routes. These are not needed in plugin mode. - if !a.isPlugin { - apiv2.HandleFunc("/login", a.handleLogin).Methods("POST") - apiv2.HandleFunc("/logout", a.sessionRequired(a.handleLogout)).Methods("POST") - apiv2.HandleFunc("/register", a.handleRegister).Methods("POST") - apiv2.HandleFunc("/teams/{teamID}/regenerate_signup_token", a.sessionRequired(a.handlePostTeamRegenerateSignupToken)).Methods("POST") - } + a.registerUsersRoutes(apiv2) + a.registerAuthRoutes(apiv2) + a.registerMembersRoutes(apiv2) + a.registerCategoriesRoutes(apiv2) + a.registerSharingRoutes(apiv2) + a.registerTeamsRoutes(apiv2) + a.registerAchivesRoutes(apiv2) + a.registerSubscriptionsRoutes(apiv2) + a.registerFilesRoutes(apiv2) + a.registerLimitsRoutes(apiv2) + a.registerInsightsRoutes(apiv2) + a.registerOnboardingRoutes(apiv2) + a.registerSearchRoutes(apiv2) + a.registerConfigRoutes(apiv2) + a.registerBoardsAndBlocksRoutes(apiv2) + a.registerChannelsRoutes(apiv2) + a.registerTemplatesRoutes(apiv2) + a.registerBoardsRoutes(apiv2) + a.registerBlocksRoutes(apiv2) - // Board APIs - apiv2.HandleFunc("/teams/{teamID}/channels", a.sessionRequired(a.handleSearchMyChannels)).Methods("GET") - apiv2.HandleFunc("/teams/{teamID}/channels/{channelID}", a.sessionRequired(a.handleGetChannel)).Methods("GET") - apiv2.HandleFunc("/teams/{teamID}/boards", a.sessionRequired(a.handleGetBoards)).Methods("GET") - apiv2.HandleFunc("/teams/{teamID}/boards/search", a.sessionRequired(a.handleSearchBoards)).Methods("GET") - apiv2.HandleFunc("/teams/{teamID}/templates", a.sessionRequired(a.handleGetTemplates)).Methods("GET") - apiv2.HandleFunc("/boards", a.sessionRequired(a.handleCreateBoard)).Methods("POST") - apiv2.HandleFunc("/boards/search", a.sessionRequired(a.handleSearchAllBoards)).Methods("GET") - apiv2.HandleFunc("/boards/{boardID}", a.attachSession(a.handleGetBoard, false)).Methods("GET") - apiv2.HandleFunc("/boards/{boardID}", a.sessionRequired(a.handlePatchBoard)).Methods("PATCH") - apiv2.HandleFunc("/boards/{boardID}", a.sessionRequired(a.handleDeleteBoard)).Methods("DELETE") - apiv2.HandleFunc("/boards/{boardID}/duplicate", a.sessionRequired(a.handleDuplicateBoard)).Methods("POST") - apiv2.HandleFunc("/boards/{boardID}/undelete", a.sessionRequired(a.handleUndeleteBoard)).Methods("POST") - apiv2.HandleFunc("/boards/{boardID}/blocks", a.attachSession(a.handleGetBlocks, false)).Methods("GET") - apiv2.HandleFunc("/boards/{boardID}/blocks", a.sessionRequired(a.handlePostBlocks)).Methods("POST") - apiv2.HandleFunc("/boards/{boardID}/blocks", a.sessionRequired(a.handlePatchBlocks)).Methods("PATCH") - apiv2.HandleFunc("/boards/{boardID}/blocks/{blockID}", a.sessionRequired(a.handleDeleteBlock)).Methods("DELETE") - apiv2.HandleFunc("/boards/{boardID}/blocks/{blockID}", a.sessionRequired(a.handlePatchBlock)).Methods("PATCH") - apiv2.HandleFunc("/boards/{boardID}/blocks/{blockID}/undelete", a.sessionRequired(a.handleUndeleteBlock)).Methods("POST") - apiv2.HandleFunc("/boards/{boardID}/blocks/{blockID}/duplicate", a.sessionRequired(a.handleDuplicateBlock)).Methods("POST") - apiv2.HandleFunc("/boards/{boardID}/metadata", a.sessionRequired(a.handleGetBoardMetadata)).Methods("GET") - - // Member APIs - apiv2.HandleFunc("/boards/{boardID}/members", a.sessionRequired(a.handleGetMembersForBoard)).Methods("GET") - apiv2.HandleFunc("/boards/{boardID}/members", a.sessionRequired(a.handleAddMember)).Methods("POST") - apiv2.HandleFunc("/boards/{boardID}/members/{userID}", a.sessionRequired(a.handleUpdateMember)).Methods("PUT") - apiv2.HandleFunc("/boards/{boardID}/members/{userID}", a.sessionRequired(a.handleDeleteMember)).Methods("DELETE") - apiv2.HandleFunc("/boards/{boardID}/join", a.sessionRequired(a.handleJoinBoard)).Methods("POST") - apiv2.HandleFunc("/boards/{boardID}/leave", a.sessionRequired(a.handleLeaveBoard)).Methods("POST") - - // Sharing APIs - apiv2.HandleFunc("/boards/{boardID}/sharing", a.sessionRequired(a.handlePostSharing)).Methods("POST") - apiv2.HandleFunc("/boards/{boardID}/sharing", a.sessionRequired(a.handleGetSharing)).Methods("GET") - - // Team APIs - apiv2.HandleFunc("/teams", a.sessionRequired(a.handleGetTeams)).Methods("GET") - apiv2.HandleFunc("/teams/{teamID}", a.sessionRequired(a.handleGetTeam)).Methods("GET") - apiv2.HandleFunc("/teams/{teamID}/users", a.sessionRequired(a.handleGetTeamUsers)).Methods("GET") - apiv2.HandleFunc("/teams/{teamID}/archive/export", a.sessionRequired(a.handleArchiveExportTeam)).Methods("GET") - apiv2.HandleFunc("/teams/{teamID}/{boardID}/files", a.sessionRequired(a.handleUploadFile)).Methods("POST") - - // User APIs - apiv2.HandleFunc("/users/me", a.sessionRequired(a.handleGetMe)).Methods("GET") - apiv2.HandleFunc("/users/me/memberships", a.sessionRequired(a.handleGetMyMemberships)).Methods("GET") - apiv2.HandleFunc("/users/{userID}", a.sessionRequired(a.handleGetUser)).Methods("GET") - apiv2.HandleFunc("/users/{userID}/changepassword", a.sessionRequired(a.handleChangePassword)).Methods("POST") - apiv2.HandleFunc("/users/{userID}/config", a.sessionRequired(a.handleUpdateUserConfig)).Methods(http.MethodPut) - - // BoardsAndBlocks APIs - apiv2.HandleFunc("/boards-and-blocks", a.sessionRequired(a.handleCreateBoardsAndBlocks)).Methods("POST") - apiv2.HandleFunc("/boards-and-blocks", a.sessionRequired(a.handlePatchBoardsAndBlocks)).Methods("PATCH") - apiv2.HandleFunc("/boards-and-blocks", a.sessionRequired(a.handleDeleteBoardsAndBlocks)).Methods("DELETE") - - // Auth APIs - apiv2.HandleFunc("/clientConfig", a.getClientConfig).Methods("GET") - - // Category APIs - apiv2.HandleFunc("/teams/{teamID}/categories", a.sessionRequired(a.handleCreateCategory)).Methods(http.MethodPost) - apiv2.HandleFunc("/teams/{teamID}/categories/{categoryID}", a.sessionRequired(a.handleUpdateCategory)).Methods(http.MethodPut) - apiv2.HandleFunc("/teams/{teamID}/categories/{categoryID}", a.sessionRequired(a.handleDeleteCategory)).Methods(http.MethodDelete) - - // Category Block APIs - apiv2.HandleFunc("/teams/{teamID}/categories", a.sessionRequired(a.handleGetUserCategoryBoards)).Methods(http.MethodGet) - apiv2.HandleFunc("/teams/{teamID}/categories/{categoryID}/boards/{boardID}", a.sessionRequired(a.handleUpdateCategoryBoard)).Methods(http.MethodPost) - - // Get Files API - apiv2.HandleFunc("/files/teams/{teamID}/{boardID}/{filename}", a.attachSession(a.handleServeFile, false)).Methods("GET") - - // Subscription APIs - apiv2.HandleFunc("/subscriptions", a.sessionRequired(a.handleCreateSubscription)).Methods("POST") - apiv2.HandleFunc("/subscriptions/{blockID}/{subscriberID}", a.sessionRequired(a.handleDeleteSubscription)).Methods("DELETE") - apiv2.HandleFunc("/subscriptions/{subscriberID}", a.sessionRequired(a.handleGetSubscriptions)).Methods("GET") - - // Onboarding tour endpoints APIs - apiv2.HandleFunc("/teams/{teamID}/onboard", a.sessionRequired(a.handleOnboard)).Methods(http.MethodPost) - - // Archive APIs - apiv2.HandleFunc("/boards/{boardID}/archive/export", a.sessionRequired(a.handleArchiveExportBoard)).Methods("GET") - apiv2.HandleFunc("/teams/{teamID}/archive/import", a.sessionRequired(a.handleArchiveImport)).Methods("POST") - - // limits - apiv2.HandleFunc("/limits", a.sessionRequired(a.handleCloudLimits)).Methods("GET") - apiv2.HandleFunc("/teams/{teamID}/notifyadminupgrade", a.sessionRequired(a.handleNotifyAdminUpgrade)).Methods(http.MethodPost) - - // System APIs - r.HandleFunc("/hello", a.handleHello).Methods("GET") + // System routes are outside the /api/v2 path + a.registerSystemRoutes(r) } func (a *API) RegisterAdminRoutes(r *mux.Router) { @@ -222,34 +143,6 @@ func (a *API) requireCSRFToken(next http.Handler) http.Handler { }) } -func (a *API) getClientConfig(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /clientConfig getClientConfig - // - // Returns the client configuration - // - // --- - // produces: - // - application/json - // responses: - // '200': - // description: success - // schema: - // "$ref": "#/definitions/ClientConfig" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - clientConfig := a.app.GetClientConfig() - - configData, err := json.Marshal(clientConfig) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - jsonBytesResponse(w, http.StatusOK, configData) -} - func (a *API) checkCSRFToken(r *http.Request) bool { token := r.Header.Get(HeaderRequestedWith) return token == HeaderRequestedWithXML @@ -272,4244 +165,6 @@ func (a *API) hasValidReadTokenForBoard(r *http.Request, boardID string) bool { return isValid } -func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /boards/{boardID}/blocks getBlocks - // - // Returns blocks - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // - name: parent_id - // in: query - // description: ID of parent block, omit to specify all blocks - // required: false - // type: string - // - name: type - // in: query - // description: Type of blocks to return, omit to specify all types - // required: false - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // type: array - // items: - // "$ref": "#/definitions/Block" - // '404': - // description: board not found - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - query := r.URL.Query() - parentID := query.Get("parent_id") - blockType := query.Get("type") - all := query.Get("all") - blockID := query.Get("block_id") - boardID := mux.Vars(r)["boardID"] - - userID := getUserID(r) - - hasValidReadToken := a.hasValidReadTokenForBoard(r, boardID) - if userID == "" && !hasValidReadToken { - a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "", PermissionError{"access denied to board"}) - return - } - - board, err := a.app.GetBoard(boardID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - if board == nil { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "Board not found", nil) - return - } - - if !hasValidReadToken { - if board.IsTemplate && board.Type == model.BoardTypeOpen { - if board.TeamID != model.GlobalTeamID && !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board template"}) - return - } - } else { - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) - return - } - } - } - - auditRec := a.makeAuditRecord(r, "getBlocks", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("boardID", boardID) - auditRec.AddMeta("parentID", parentID) - auditRec.AddMeta("blockType", blockType) - auditRec.AddMeta("all", all) - auditRec.AddMeta("blockID", blockID) - - var blocks []model.Block - var block *model.Block - switch { - case all != "": - blocks, err = a.app.GetBlocksForBoard(boardID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - case blockID != "": - block, err = a.app.GetBlockByID(blockID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - if block != nil { - if block.BoardID != boardID { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) - return - } - - blocks = append(blocks, *block) - } - default: - blocks, err = a.app.GetBlocks(boardID, parentID, blockType) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - } - - a.logger.Debug("GetBlocks", - mlog.String("boardID", boardID), - mlog.String("parentID", parentID), - mlog.String("blockType", blockType), - mlog.String("blockID", blockID), - mlog.Int("block_count", len(blocks)), - ) - - var bErr error - blocks, bErr = a.app.ApplyCloudLimits(blocks) - if bErr != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", bErr) - return - } - - json, err := json.Marshal(blocks) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, json) - - auditRec.AddMeta("blockCount", len(blocks)) - auditRec.Success() -} - -func (a *API) handleCreateCategory(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /teams/{teamID}/categories createCategory - // - // Create a category for boards - // - // --- - // produces: - // - application/json - // parameters: - // - name: teamID - // in: path - // description: Team ID - // required: true - // type: string - // - name: Body - // in: body - // description: category to create - // required: true - // schema: - // "$ref": "#/definitions/Category" - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // "$ref": "#/definitions/Category" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - requestBody, err := ioutil.ReadAll(r.Body) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - var category model.Category - - err = json.Unmarshal(requestBody, &category) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - auditRec := a.makeAuditRecord(r, "createCategory", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - - ctx := r.Context() - session := ctx.Value(sessionContextKey).(*model.Session) - - // user can only create category for themselves - if category.UserID != session.UserID { - a.errorResponse( - w, - r.URL.Path, - http.StatusBadRequest, - fmt.Sprintf("userID %s and category userID %s mismatch", session.UserID, category.UserID), - nil, - ) - return - } - - vars := mux.Vars(r) - teamID := vars["teamID"] - - if category.TeamID != teamID { - a.errorResponse( - w, - r.URL.Path, - http.StatusBadRequest, - "teamID mismatch", - nil, - ) - return - } - - createdCategory, err := a.app.CreateCategory(&category) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - data, err := json.Marshal(createdCategory) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, data) - auditRec.AddMeta("categoryID", createdCategory.ID) - auditRec.Success() -} - -func (a *API) handleUpdateCategory(w http.ResponseWriter, r *http.Request) { - // swagger:operation PUT /teams/{teamID}/categories/{categoryID} updateCategory - // - // Create a category for boards - // - // --- - // produces: - // - application/json - // parameters: - // - name: teamID - // in: path - // description: Team ID - // required: true - // type: string - // - name: categoryID - // in: path - // description: Category ID - // required: true - // type: string - // - name: Body - // in: body - // description: category to update - // required: true - // schema: - // "$ref": "#/definitions/Category" - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // "$ref": "#/definitions/Category" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - vars := mux.Vars(r) - categoryID := vars["categoryID"] - - requestBody, err := ioutil.ReadAll(r.Body) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - var category model.Category - err = json.Unmarshal(requestBody, &category) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - auditRec := a.makeAuditRecord(r, "updateCategory", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - - if categoryID != category.ID { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "categoryID mismatch in patch and body", nil) - return - } - - ctx := r.Context() - session := ctx.Value(sessionContextKey).(*model.Session) - - // user can only update category for themselves - if category.UserID != session.UserID { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "user ID mismatch in session and category", nil) - return - } - - teamID := vars["teamID"] - if category.TeamID != teamID { - a.errorResponse( - w, - r.URL.Path, - http.StatusBadRequest, - "teamID mismatch", - nil, - ) - return - } - - updatedCategory, err := a.app.UpdateCategory(&category) - if err != nil { - switch { - case errors.Is(err, app.ErrorCategoryDeleted): - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", err) - case errors.Is(err, app.ErrorCategoryPermissionDenied): - // TODO: The permissions should be handled as much as possible at - // the API level, this needs to be changed - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", err) - default: - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - } - return - } - - data, err := json.Marshal(updatedCategory) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, data) - auditRec.Success() -} - -func (a *API) handleDeleteCategory(w http.ResponseWriter, r *http.Request) { - // swagger:operation DELETE /teams/{teamID}/categories/{categoryID} deleteCategory - // - // Delete a category - // - // --- - // produces: - // - application/json - // parameters: - // - name: teamID - // in: path - // description: Team ID - // required: true - // type: string - // - name: categoryID - // in: path - // description: Category ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - ctx := r.Context() - session := ctx.Value(sessionContextKey).(*model.Session) - vars := mux.Vars(r) - - userID := session.UserID - teamID := vars["teamID"] - categoryID := vars["categoryID"] - - auditRec := a.makeAuditRecord(r, "deleteCategory", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - - deletedCategory, err := a.app.DeleteCategory(categoryID, userID, teamID) - if err != nil { - switch { - case errors.Is(err, app.ErrorInvalidCategory): - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) - case errors.Is(err, app.ErrorCategoryPermissionDenied): - // TODO: The permissions should be handled as much as possible at - // the API level, this needs to be changed - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", err) - default: - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - } - return - } - - data, err := json.Marshal(deletedCategory) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, data) - auditRec.Success() -} - -func (a *API) handleGetUserCategoryBoards(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /teams/{teamID}/categories getUserCategoryBoards - // - // Gets the user's board categories - // - // --- - // produces: - // - application/json - // parameters: - // - name: teamID - // in: path - // description: Team ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // items: - // "$ref": "#/definitions/CategoryBoards" - // type: array - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - ctx := r.Context() - session := ctx.Value(sessionContextKey).(*model.Session) - userID := session.UserID - - vars := mux.Vars(r) - teamID := vars["teamID"] - - auditRec := a.makeAuditRecord(r, "getUserCategoryBoards", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - - categoryBlocks, err := a.app.GetUserCategoryBoards(userID, teamID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - data, err := json.Marshal(categoryBlocks) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, data) - auditRec.Success() -} - -func (a *API) handleUpdateCategoryBoard(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /teams/{teamID}/categories/{categoryID}/boards/{boardID} updateCategoryBoard - // - // Set the category of a board - // - // --- - // produces: - // - application/json - // parameters: - // - name: teamID - // in: path - // description: Team ID - // required: true - // type: string - // - name: categoryID - // in: path - // description: Category ID - // required: true - // type: string - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - auditRec := a.makeAuditRecord(r, "updateCategoryBoard", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - - vars := mux.Vars(r) - categoryID := vars["categoryID"] - boardID := vars["boardID"] - teamID := vars["teamID"] - - ctx := r.Context() - session := ctx.Value(sessionContextKey).(*model.Session) - userID := session.UserID - - // TODO: Check the category and the team matches - err := a.app.AddUpdateUserCategoryBoard(teamID, userID, categoryID, boardID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, []byte("ok")) - auditRec.Success() -} - -func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /boards/{boardID}/blocks updateBlocks - // - // Insert blocks. The specified IDs will only be used to link - // blocks with existing ones, the rest will be replaced by server - // generated IDs - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // - name: disable_notify - // in: query - // description: Disables notifications (for bulk data inserting) - // required: false - // type: bool - // - name: Body - // in: body - // description: array of blocks to insert or update - // required: true - // schema: - // type: array - // items: - // "$ref": "#/definitions/Block" - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // items: - // $ref: '#/definitions/Block' - // type: array - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - boardID := mux.Vars(r)["boardID"] - userID := getUserID(r) - - val := r.URL.Query().Get("disable_notify") - disableNotify := val == True - - // in phase 1 we use "manage_board_cards", but we would have to - // check on specific actions for phase 2 - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"}) - return - } - - requestBody, err := ioutil.ReadAll(r.Body) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - var blocks []model.Block - - err = json.Unmarshal(requestBody, &blocks) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - for _, block := range blocks { - // Error checking - if len(block.Type) < 1 { - message := fmt.Sprintf("missing type for block id %s", block.ID) - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) - return - } - - if block.CreateAt < 1 { - message := fmt.Sprintf("invalid createAt for block id %s", block.ID) - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) - return - } - - if block.UpdateAt < 1 { - message := fmt.Sprintf("invalid UpdateAt for block id %s", block.ID) - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) - return - } - - if block.BoardID != boardID { - message := fmt.Sprintf("invalid BoardID for block id %s", block.ID) - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) - return - } - } - - blocks = model.GenerateBlockIDs(blocks, a.logger) - - auditRec := a.makeAuditRecord(r, "postBlocks", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("disable_notify", disableNotify) - - ctx := r.Context() - session := ctx.Value(sessionContextKey).(*model.Session) - - model.StampModificationMetadata(userID, blocks, auditRec) - - // this query param exists when creating template from board, or board from template - sourceBoardID := r.URL.Query().Get("sourceBoardID") - if sourceBoardID != "" { - if updateFileIDsErr := a.app.CopyCardFiles(sourceBoardID, blocks); updateFileIDsErr != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", updateFileIDsErr) - return - } - } - - newBlocks, err := a.app.InsertBlocks(blocks, session.UserID, !disableNotify) - if err != nil { - if errors.Is(err, app.ErrViewsLimitReached) { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, err.Error(), err) - } else { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - } - - return - } - - a.logger.Debug("POST Blocks", - mlog.Int("block_count", len(blocks)), - mlog.Bool("disable_notify", disableNotify), - ) - - json, err := json.Marshal(newBlocks) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, json) - - auditRec.AddMeta("blockCount", len(blocks)) - auditRec.Success() -} - -func (a *API) handleUpdateUserConfig(w http.ResponseWriter, r *http.Request) { - // swagger:operation PATCH /users/{userID}/config updateUserConfig - // - // Updates user config - // - // --- - // produces: - // - application/json - // parameters: - // - name: userID - // in: path - // description: User ID - // required: true - // type: string - // - name: Body - // in: body - // description: User config patch to apply - // required: true - // schema: - // "$ref": "#/definitions/UserPropPatch" - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - requestBody, err := ioutil.ReadAll(r.Body) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - var patch *model.UserPropPatch - err = json.Unmarshal(requestBody, &patch) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - vars := mux.Vars(r) - userID := vars["userID"] - - ctx := r.Context() - session := ctx.Value(sessionContextKey).(*model.Session) - - auditRec := a.makeAuditRecord(r, "updateUserConfig", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - - // a user can update only own config - if userID != session.UserID { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", nil) - return - } - - updatedConfig, err := a.app.UpdateUserConfig(userID, *patch) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - data, err := json.Marshal(updatedConfig) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, data) - auditRec.Success() -} - -func (a *API) handleGetUser(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /users/{userID} getUser - // - // Returns a user - // - // --- - // produces: - // - application/json - // parameters: - // - name: userID - // in: path - // description: User ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // "$ref": "#/definitions/User" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - vars := mux.Vars(r) - userID := vars["userID"] - - auditRec := a.makeAuditRecord(r, "postBlocks", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("userID", userID) - - user, err := a.app.GetUser(userID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - userData, err := json.Marshal(user) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, userData) - auditRec.Success() -} - -func (a *API) handleGetMe(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /users/me getMe - // - // Returns the currently logged-in user - // - // --- - // produces: - // - application/json - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // "$ref": "#/definitions/User" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - userID := getUserID(r) - - var user *model.User - var err error - - auditRec := a.makeAuditRecord(r, "getMe", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - - if userID == model.SingleUser { - ws, _ := a.app.GetRootTeam() - now := utils.GetMillis() - user = &model.User{ - ID: model.SingleUser, - Username: model.SingleUser, - Email: model.SingleUser, - CreateAt: ws.UpdateAt, - UpdateAt: now, - } - } else { - user, err = a.app.GetUser(userID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - } - - userData, err := json.Marshal(user) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - jsonBytesResponse(w, http.StatusOK, userData) - - auditRec.AddMeta("userID", user.ID) - auditRec.Success() -} - -func (a *API) handleGetMyMemberships(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /users/me/memberships getMyMemberships - // - // Returns the currently users board memberships - // - // --- - // produces: - // - application/json - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // type: array - // items: - // "$ref": "#/definitions/BoardMember" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - userID := getUserID(r) - - auditRec := a.makeAuditRecord(r, "getMyBoardMemberships", audit.Fail) - auditRec.AddMeta("userID", userID) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - - members, err := a.app.GetMembersForUser(userID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - membersData, err := json.Marshal(members) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, membersData) - - auditRec.Success() -} - -func (a *API) handleDeleteBlock(w http.ResponseWriter, r *http.Request) { - // swagger:operation DELETE /boards/{boardID}/blocks/{blockID} deleteBlock - // - // Deletes a block - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // - name: blockID - // in: path - // description: ID of block to delete - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // '404': - // description: block not found - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - userID := getUserID(r) - vars := mux.Vars(r) - boardID := vars["boardID"] - blockID := vars["blockID"] - - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"}) - return - } - - block, err := a.app.GetBlockByID(blockID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - if block == nil || block.BoardID != boardID { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) - return - } - - auditRec := a.makeAuditRecord(r, "deleteBlock", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("boardID", boardID) - auditRec.AddMeta("blockID", blockID) - - err = a.app.DeleteBlock(blockID, userID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("DELETE Block", mlog.String("boardID", boardID), mlog.String("blockID", blockID)) - jsonStringResponse(w, http.StatusOK, "{}") - - auditRec.Success() -} - -func (a *API) handleUndeleteBlock(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /boards/{boardID}/blocks/{blockID}/undelete undeleteBlock - // - // Undeletes a block - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // - name: blockID - // in: path - // description: ID of block to undelete - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // "$ref": "#/definitions/BlockPatch" - // '404': - // description: block not found - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - ctx := r.Context() - session := ctx.Value(sessionContextKey).(*model.Session) - userID := session.UserID - - vars := mux.Vars(r) - blockID := vars["blockID"] - boardID := vars["boardID"] - - board, err := a.app.GetBoard(boardID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - if board == nil { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) - return - } - - block, err := a.app.GetLastBlockHistoryEntry(blockID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - if block == nil { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) - return - } - - if board.ID != block.BoardID { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) - return - } - - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modify board members"}) - return - } - - auditRec := a.makeAuditRecord(r, "undeleteBlock", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("blockID", blockID) - - undeletedBlock, err := a.app.UndeleteBlock(blockID, userID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - undeletedBlockData, err := json.Marshal(undeletedBlock) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("UNDELETE Block", mlog.String("blockID", blockID)) - jsonBytesResponse(w, http.StatusOK, undeletedBlockData) - - auditRec.Success() -} - -func (a *API) handleUndeleteBoard(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /boards/{boardID}/undelete undeleteBoard - // - // Undeletes a board - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: ID of board to undelete - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - ctx := r.Context() - session := ctx.Value(sessionContextKey).(*model.Session) - userID := session.UserID - - vars := mux.Vars(r) - boardID := vars["boardID"] - - auditRec := a.makeAuditRecord(r, "undeleteBoard", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("boardID", boardID) - - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionDeleteBoard) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to undelete board"}) - return - } - - err := a.app.UndeleteBoard(boardID, userID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("UNDELETE Board", mlog.String("boardID", boardID)) - jsonStringResponse(w, http.StatusOK, "{}") - - auditRec.Success() -} - -func (a *API) handlePatchBlock(w http.ResponseWriter, r *http.Request) { - // swagger:operation PATCH /boards/{boardID}/blocks/{blockID} patchBlock - // - // Partially updates a block - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // - name: blockID - // in: path - // description: ID of block to patch - // required: true - // type: string - // - name: Body - // in: body - // description: block patch to apply - // required: true - // schema: - // "$ref": "#/definitions/BlockPatch" - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // '404': - // description: block not found - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - userID := getUserID(r) - vars := mux.Vars(r) - boardID := vars["boardID"] - blockID := vars["blockID"] - - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"}) - return - } - - block, err := a.app.GetBlockByID(blockID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - if block == nil || block.BoardID != boardID { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) - return - } - - requestBody, err := ioutil.ReadAll(r.Body) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - var patch *model.BlockPatch - err = json.Unmarshal(requestBody, &patch) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - auditRec := a.makeAuditRecord(r, "patchBlock", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("boardID", boardID) - auditRec.AddMeta("blockID", blockID) - - err = a.app.PatchBlock(blockID, patch, userID) - if errors.Is(err, app.ErrPatchUpdatesLimitedCards) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", err) - return - } - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("PATCH Block", mlog.String("boardID", boardID), mlog.String("blockID", blockID)) - jsonStringResponse(w, http.StatusOK, "{}") - - auditRec.Success() -} - -func (a *API) handlePatchBlocks(w http.ResponseWriter, r *http.Request) { - // swagger:operation PATCH /boards/{boardID}/blocks/ patchBlocks - // - // Partially updates batch of blocks - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Workspace ID - // required: true - // type: string - // - name: Body - // in: body - // description: block Ids and block patches to apply - // required: true - // schema: - // "$ref": "#/definitions/BlockPatchBatch" - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - ctx := r.Context() - session := ctx.Value(sessionContextKey).(*model.Session) - userID := session.UserID - - vars := mux.Vars(r) - teamID := vars["teamID"] - - requestBody, err := ioutil.ReadAll(r.Body) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - var patches *model.BlockPatchBatch - err = json.Unmarshal(requestBody, &patches) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - auditRec := a.makeAuditRecord(r, "patchBlocks", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - for i := range patches.BlockIDs { - auditRec.AddMeta("block_"+strconv.FormatInt(int64(i), 10), patches.BlockIDs[i]) - } - - for _, blockID := range patches.BlockIDs { - var block *model.Block - block, err = a.app.GetBlockByID(blockID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"}) - return - } - if !a.permissions.HasPermissionToBoard(userID, block.BoardID, model.PermissionManageBoardCards) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"}) - return - } - } - - err = a.app.PatchBlocks(teamID, patches, userID) - if errors.Is(err, app.ErrPatchUpdatesLimitedCards) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", err) - return - } - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("PATCH Blocks", mlog.String("patches", strconv.Itoa(len(patches.BlockIDs)))) - jsonStringResponse(w, http.StatusOK, "{}") - - auditRec.Success() -} - -// Sharing - -func (a *API) handleGetSharing(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /boards/{boardID}/sharing getSharing - // - // Returns sharing information for a board - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // "$ref": "#/definitions/Sharing" - // '404': - // description: board not found - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - vars := mux.Vars(r) - boardID := vars["boardID"] - - userID := getUserID(r) - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionShareBoard) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to sharing the board"}) - return - } - - auditRec := a.makeAuditRecord(r, "getSharing", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("boardID", boardID) - - sharing, err := a.app.GetSharing(boardID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - if sharing == nil { - jsonStringResponse(w, http.StatusOK, "") - return - } - - sharingData, err := json.Marshal(sharing) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, sharingData) - - a.logger.Debug("GET sharing", - mlog.String("boardID", boardID), - mlog.String("shareID", sharing.ID), - mlog.Bool("enabled", sharing.Enabled), - ) - auditRec.AddMeta("shareID", sharing.ID) - auditRec.AddMeta("enabled", sharing.Enabled) - auditRec.Success() -} - -func (a *API) handlePostSharing(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /boards/{boardID}/sharing postSharing - // - // Sets sharing information for a board - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // - name: Body - // in: body - // description: sharing information for a root block - // required: true - // schema: - // "$ref": "#/definitions/Sharing" - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - boardID := mux.Vars(r)["boardID"] - - userID := getUserID(r) - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionShareBoard) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to sharing the board"}) - return - } - - requestBody, err := ioutil.ReadAll(r.Body) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - var sharing model.Sharing - err = json.Unmarshal(requestBody, &sharing) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - // Stamp boardID from the URL - sharing.ID = boardID - - auditRec := a.makeAuditRecord(r, "postSharing", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("shareID", sharing.ID) - auditRec.AddMeta("enabled", sharing.Enabled) - - // Stamp ModifiedBy - modifiedBy := userID - if userID == model.SingleUser { - modifiedBy = "" - } - sharing.ModifiedBy = modifiedBy - - if userID == model.SingleUser { - userID = "" - } - - if !a.app.GetClientConfig().EnablePublicSharedBoards { - a.logger.Warn( - "Attempt to turn on sharing for board via API failed, sharing off in configuration.", - mlog.String("boardID", sharing.ID), - mlog.String("userID", userID)) - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "Turning on sharing for board failed, see log for details.", nil) - return - } - - sharing.ModifiedBy = userID - - err = a.app.UpsertSharing(sharing) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonStringResponse(w, http.StatusOK, "{}") - - a.logger.Debug("POST sharing", mlog.String("sharingID", sharing.ID)) - auditRec.Success() -} - -// Team - -func (a *API) handleGetTeams(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /teams getTeams - // - // Returns information of all the teams - // - // --- - // produces: - // - application/json - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // type: array - // items: - // "$ref": "#/definitions/Team" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - userID := getUserID(r) - - teams, err := a.app.GetTeamsForUser(userID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - } - - auditRec := a.makeAuditRecord(r, "getTeams", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("teamCount", len(teams)) - - data, err := json.Marshal(teams) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, data) - auditRec.Success() -} - -func (a *API) handleGetTeam(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /teams/{teamID} getTeam - // - // Returns information of the root team - // - // --- - // produces: - // - application/json - // parameters: - // - name: teamID - // in: path - // description: Team ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // "$ref": "#/definitions/Team" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - vars := mux.Vars(r) - teamID := vars["teamID"] - userID := getUserID(r) - - if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"}) - return - } - - var team *model.Team - var err error - - if a.MattermostAuth { - team, err = a.app.GetTeam(teamID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - } - if team == nil { - a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "invalid team", nil) - return - } - } else { - team, err = a.app.GetRootTeam() - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - } - - auditRec := a.makeAuditRecord(r, "getTeam", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("resultTeamID", team.ID) - - data, err := json.Marshal(team) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, data) - auditRec.Success() -} - -func (a *API) handlePostTeamRegenerateSignupToken(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /teams/{teamID}/regenerate_signup_token regenerateSignupToken - // - // Regenerates the signup token for the root team - // - // --- - // produces: - // - application/json - // parameters: - // - name: teamID - // in: path - // description: Team ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - if a.MattermostAuth { - a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil) - return - } - - team, err := a.app.GetRootTeam() - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - auditRec := a.makeAuditRecord(r, "regenerateSignupToken", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - - team.SignupToken = utils.NewID(utils.IDTypeToken) - - err = a.app.UpsertTeamSignupToken(*team) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonStringResponse(w, http.StatusOK, "{}") - auditRec.Success() -} - -// File upload - -func (a *API) handleServeFile(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /files/teams/{teamID}/{boardID}/{filename} getFile - // - // Returns the contents of an uploaded file - // - // --- - // produces: - // - application/json - // - image/jpg - // - image/png - // - image/gif - // parameters: - // - name: teamID - // in: path - // description: Team ID - // required: true - // type: string - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // - name: filename - // in: path - // description: name of the file - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // '404': - // description: file not found - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - vars := mux.Vars(r) - boardID := vars["boardID"] - filename := vars["filename"] - userID := getUserID(r) - - hasValidReadToken := a.hasValidReadTokenForBoard(r, boardID) - if userID == "" && !hasValidReadToken { - a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "", nil) - return - } - - if !hasValidReadToken && !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) - return - } - - board, err := a.app.GetBoard(boardID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - if board == nil { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) - return - } - - auditRec := a.makeAuditRecord(r, "getFile", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("boardID", boardID) - auditRec.AddMeta("teamID", board.TeamID) - auditRec.AddMeta("filename", filename) - - contentType := "image/jpg" - - fileExtension := strings.ToLower(filepath.Ext(filename)) - if fileExtension == "png" { - contentType = "image/png" - } - - if fileExtension == "gif" { - contentType = "image/gif" - } - - w.Header().Set("Content-Type", contentType) - - fileInfo, err := a.app.GetFileInfo(filename) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - if fileInfo != nil && fileInfo.Archived { - fileMetadata := map[string]interface{}{ - "archived": true, - "name": fileInfo.Name, - "size": fileInfo.Size, - "extension": fileInfo.Extension, - } - - data, jsonErr := json.Marshal(fileMetadata) - if jsonErr != nil { - a.logger.Error("failed to marshal archived file metadata", mlog.String("filename", filename), mlog.Err(jsonErr)) - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", jsonErr) - return - } - - jsonBytesResponse(w, http.StatusBadRequest, data) - return - } - - fileReader, err := a.app.GetFileReader(board.TeamID, boardID, filename) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - defer fileReader.Close() - http.ServeContent(w, r, filename, time.Now(), fileReader) - auditRec.Success() -} - -// FileUploadResponse is the response to a file upload -// swagger:model -type FileUploadResponse struct { - // The FileID to retrieve the uploaded file - // required: true - FileID string `json:"fileId"` -} - -func FileUploadResponseFromJSON(data io.Reader) (*FileUploadResponse, error) { - var fileUploadResponse FileUploadResponse - - if err := json.NewDecoder(data).Decode(&fileUploadResponse); err != nil { - return nil, err - } - return &fileUploadResponse, nil -} - -func (a *API) handleUploadFile(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /teams/{teamID}/boards/{boardID}/files uploadFile - // - // Upload a binary file, attached to a root block - // - // --- - // consumes: - // - multipart/form-data - // produces: - // - application/json - // parameters: - // - name: teamID - // in: path - // description: ID of the team - // required: true - // type: string - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // - name: uploaded file - // in: formData - // type: file - // description: The file to upload - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // "$ref": "#/definitions/FileUploadResponse" - // '404': - // description: board not found - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - vars := mux.Vars(r) - boardID := vars["boardID"] - userID := getUserID(r) - - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"}) - return - } - - board, err := a.app.GetBoard(boardID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - if board == nil { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) - return - } - - if a.app.GetConfig().MaxFileSize > 0 { - r.Body = http.MaxBytesReader(w, r.Body, a.app.GetConfig().MaxFileSize) - } - - file, handle, err := r.FormFile(UploadFormFileKey) - if err != nil { - if strings.HasSuffix(err.Error(), "http: request body too large") { - a.errorResponse(w, r.URL.Path, http.StatusRequestEntityTooLarge, "", err) - return - } - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) - return - } - defer file.Close() - - auditRec := a.makeAuditRecord(r, "uploadFile", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("boardID", boardID) - auditRec.AddMeta("teamID", board.TeamID) - auditRec.AddMeta("filename", handle.Filename) - - fileID, err := a.app.SaveFile(file, board.TeamID, boardID, handle.Filename) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("uploadFile", - mlog.String("filename", handle.Filename), - mlog.String("fileID", fileID), - ) - data, err := json.Marshal(FileUploadResponse{FileID: fileID}) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, data) - - auditRec.AddMeta("fileID", fileID) - auditRec.Success() -} - -func (a *API) handleGetTeamUsers(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /teams/{teamID}/users getTeamUsers - // - // Returns team users - // - // --- - // produces: - // - application/json - // parameters: - // - name: teamID - // in: path - // description: Team ID - // required: true - // type: string - // - name: search - // in: query - // description: string to filter users list - // required: false - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // type: array - // items: - // "$ref": "#/definitions/User" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - vars := mux.Vars(r) - teamID := vars["teamID"] - userID := getUserID(r) - query := r.URL.Query() - searchQuery := query.Get("search") - - if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "Access denied to team", PermissionError{"access denied to team"}) - return - } - - auditRec := a.makeAuditRecord(r, "getUsers", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - - users, err := a.app.SearchTeamUsers(teamID, searchQuery) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "searchQuery="+searchQuery, err) - return - } - - data, err := json.Marshal(users) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, data) - - auditRec.AddMeta("userCount", len(users)) - auditRec.Success() -} - -func (a *API) handleSearchMyChannels(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /teams/{teamID}/channels searchMyChannels - // - // Returns the user available channels - // - // --- - // produces: - // - application/json - // parameters: - // - name: teamID - // in: path - // description: Team ID - // required: true - // type: string - // - name: search - // in: query - // description: string to filter channels list - // required: false - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // type: array - // items: - // "$ref": "#/definitions/Channel" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - if !a.MattermostAuth { - a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in standalone mode", nil) - return - } - - query := r.URL.Query() - searchQuery := query.Get("search") - - teamID := mux.Vars(r)["teamID"] - userID := getUserID(r) - - if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"}) - return - } - - auditRec := a.makeAuditRecord(r, "searchMyChannels", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("teamID", teamID) - - channels, err := a.app.SearchUserChannels(teamID, userID, searchQuery) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("GetUserChannels", - mlog.String("teamID", teamID), - mlog.Int("channelsCount", len(channels)), - ) - - data, err := json.Marshal(channels) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - // response - jsonBytesResponse(w, http.StatusOK, data) - - auditRec.AddMeta("channelsCount", len(channels)) - auditRec.Success() -} - -func (a *API) handleGetChannel(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /teams/{teamID}/channels/{channelID} getChannel - // - // Returns the requested channel - // - // --- - // produces: - // - application/json - // parameters: - // - name: teamID - // in: path - // description: Team ID - // required: true - // type: string - // - name: channelID - // in: path - // description: Channel ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // type: array - // items: - // "$ref": "#/definitions/Channel" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - if !a.MattermostAuth { - a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in standalone mode", nil) - return - } - - teamID := mux.Vars(r)["teamID"] - channelID := mux.Vars(r)["channelID"] - userID := getUserID(r) - - if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"}) - return - } - - if !a.permissions.HasPermissionToChannel(userID, channelID, model.PermissionReadChannel) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to channel"}) - return - } - - auditRec := a.makeAuditRecord(r, "getChannel", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("teamID", teamID) - auditRec.AddMeta("channelID", teamID) - - channel, err := a.app.GetChannel(teamID, channelID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("GetChannel", - mlog.String("teamID", teamID), - mlog.String("channelID", channelID), - ) - - if channel.TeamId != teamID { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) - return - } - - data, err := json.Marshal(channel) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - // response - jsonBytesResponse(w, http.StatusOK, data) - - auditRec.Success() -} - -func (a *API) handleGetBoards(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /teams/{teamID}/boards getBoards - // - // Returns team boards - // - // --- - // produces: - // - application/json - // parameters: - // - name: teamID - // in: path - // description: Team ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // type: array - // items: - // "$ref": "#/definitions/Board" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - teamID := mux.Vars(r)["teamID"] - userID := getUserID(r) - - if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"}) - return - } - - auditRec := a.makeAuditRecord(r, "getBoards", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("teamID", teamID) - - // retrieve boards list - boards, err := a.app.GetBoardsForUserAndTeam(userID, teamID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("GetBoards", - mlog.String("teamID", teamID), - mlog.Int("boardsCount", len(boards)), - ) - - data, err := json.Marshal(boards) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - // response - jsonBytesResponse(w, http.StatusOK, data) - - auditRec.AddMeta("boardsCount", len(boards)) - auditRec.Success() -} - -func (a *API) handleGetTemplates(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /teams/{teamID}/templates getTemplates - // - // Returns team templates - // - // --- - // produces: - // - application/json - // parameters: - // - name: teamID - // in: path - // description: Team ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // type: array - // items: - // "$ref": "#/definitions/Board" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - teamID := mux.Vars(r)["teamID"] - userID := getUserID(r) - - if teamID != model.GlobalTeamID && !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"}) - return - } - - auditRec := a.makeAuditRecord(r, "getTemplates", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("teamID", teamID) - - // retrieve boards list - boards, err := a.app.GetTemplateBoards(teamID, userID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - results := []*model.Board{} - for _, board := range boards { - if board.Type == model.BoardTypeOpen { - results = append(results, board) - } else if a.permissions.HasPermissionToBoard(userID, board.ID, model.PermissionViewBoard) { - results = append(results, board) - } - } - - a.logger.Debug("GetTemplates", - mlog.String("teamID", teamID), - mlog.Int("boardsCount", len(results)), - ) - - data, err := json.Marshal(results) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - // response - jsonBytesResponse(w, http.StatusOK, data) - - auditRec.AddMeta("templatesCount", len(results)) - auditRec.Success() -} - -// subscriptions - -func (a *API) handleCreateSubscription(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /subscriptions createSubscription - // - // Creates a subscription to a block for a user. The user will receive change notifications for the block. - // - // --- - // produces: - // - application/json - // parameters: - // - name: Body - // in: body - // description: subscription definition - // required: true - // schema: - // "$ref": "#/definitions/Subscription" - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // "$ref": "#/definitions/User" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - requestBody, err := ioutil.ReadAll(r.Body) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - var sub model.Subscription - - err = json.Unmarshal(requestBody, &sub) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - if err = sub.IsValid(); err != nil { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) - } - - ctx := r.Context() - session := ctx.Value(sessionContextKey).(*model.Session) - - auditRec := a.makeAuditRecord(r, "createSubscription", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("subscriber_id", sub.SubscriberID) - auditRec.AddMeta("block_id", sub.BlockID) - - // User can only create subscriptions for themselves (for now) - if session.UserID != sub.SubscriberID { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "userID and subscriberID mismatch", nil) - return - } - - // check for valid block - block, err := a.app.GetBlockByID(sub.BlockID) - if err != nil || block == nil { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "invalid blockID", err) - return - } - - subNew, err := a.app.CreateSubscription(&sub) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("CREATE subscription", - mlog.String("subscriber_id", subNew.SubscriberID), - mlog.String("block_id", subNew.BlockID), - ) - - json, err := json.Marshal(subNew) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, json) - auditRec.Success() -} - -func (a *API) handleDeleteSubscription(w http.ResponseWriter, r *http.Request) { - // swagger:operation DELETE /subscriptions/{blockID}/{subscriberID} deleteSubscription - // - // Deletes a subscription a user has for a a block. The user will no longer receive change notifications for the block. - // - // --- - // produces: - // - application/json - // parameters: - // - name: blockID - // in: path - // description: Block ID - // required: true - // type: string - // - name: subscriberID - // in: path - // description: Subscriber ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - ctx := r.Context() - session := ctx.Value(sessionContextKey).(*model.Session) - - vars := mux.Vars(r) - blockID := vars["blockID"] - subscriberID := vars["subscriberID"] - - auditRec := a.makeAuditRecord(r, "deleteSubscription", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("block_id", blockID) - auditRec.AddMeta("subscriber_id", subscriberID) - - // User can only delete subscriptions for themselves - if session.UserID != subscriberID { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "access denied", nil) - return - } - - _, err := a.app.DeleteSubscription(blockID, subscriberID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("DELETE subscription", - mlog.String("blockID", blockID), - mlog.String("subscriberID", subscriberID), - ) - jsonStringResponse(w, http.StatusOK, "{}") - - auditRec.Success() -} - -func (a *API) handleGetSubscriptions(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /subscriptions/{subscriberID} getSubscriptions - // - // Gets subscriptions for a user. - // - // --- - // produces: - // - application/json - // parameters: - // - name: subscriberID - // in: path - // description: Subscriber ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // type: array - // items: - // "$ref": "#/definitions/User" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - ctx := r.Context() - session := ctx.Value(sessionContextKey).(*model.Session) - - vars := mux.Vars(r) - subscriberID := vars["subscriberID"] - - auditRec := a.makeAuditRecord(r, "getSubscriptions", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("subscriber_id", subscriberID) - - // User can only get subscriptions for themselves (for now) - if session.UserID != subscriberID { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "access denied", nil) - return - } - - subs, err := a.app.GetSubscriptions(subscriberID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("GET subscriptions", - mlog.String("subscriberID", subscriberID), - mlog.Int("count", len(subs)), - ) - - json, err := json.Marshal(subs) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - jsonBytesResponse(w, http.StatusOK, json) - - auditRec.AddMeta("subscription_count", len(subs)) - auditRec.Success() -} - -func (a *API) handleCreateBoard(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /boards createBoard - // - // Creates a new board - // - // --- - // produces: - // - application/json - // parameters: - // - name: Body - // in: body - // description: the board to create - // required: true - // schema: - // "$ref": "#/definitions/Board" - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // $ref: '#/definitions/Board' - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - userID := getUserID(r) - - requestBody, err := ioutil.ReadAll(r.Body) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - var newBoard *model.Board - if err = json.Unmarshal(requestBody, &newBoard); err != nil { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) - return - } - - if newBoard.Type == model.BoardTypeOpen { - if !a.permissions.HasPermissionToTeam(userID, newBoard.TeamID, model.PermissionCreatePublicChannel) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to create public boards"}) - return - } - } else { - if !a.permissions.HasPermissionToTeam(userID, newBoard.TeamID, model.PermissionCreatePrivateChannel) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to create private boards"}) - return - } - } - - if err = newBoard.IsValid(); err != nil { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, err.Error(), err) - return - } - - auditRec := a.makeAuditRecord(r, "createBoard", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("teamID", newBoard.TeamID) - auditRec.AddMeta("boardType", newBoard.Type) - - // create board - board, err := a.app.CreateBoard(newBoard, userID, true) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("CreateBoard", - mlog.String("teamID", board.TeamID), - mlog.String("boardID", board.ID), - mlog.String("boardType", string(board.Type)), - mlog.String("userID", userID), - ) - - data, err := json.Marshal(board) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - // response - jsonBytesResponse(w, http.StatusOK, data) - - auditRec.Success() -} - -func (a *API) handleOnboard(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /team/{teamID}/onboard onboard - // - // Onboards a user on Boards. - // - // --- - // produces: - // - application/json - // parameters: - // - name: teamID - // in: path - // description: Team ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // type: object - // properties: - // teamID: - // type: string - // description: Team ID - // boardID: - // type: string - // description: Board ID - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - teamID := mux.Vars(r)["teamID"] - userID := getUserID(r) - - if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to create board"}) - return - } - - teamID, boardID, err := a.app.PrepareOnboardingTour(userID, teamID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - response := map[string]string{ - "teamID": teamID, - "boardID": boardID, - } - data, err := json.Marshal(response) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, data) -} - -func (a *API) handleGetBoard(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /boards/{boardID} getBoard - // - // Returns a board - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // "$ref": "#/definitions/Board" - // '404': - // description: board not found - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - boardID := mux.Vars(r)["boardID"] - userID := getUserID(r) - - hasValidReadToken := a.hasValidReadTokenForBoard(r, boardID) - if userID == "" && !hasValidReadToken { - a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "", PermissionError{"access denied to board"}) - return - } - - board, err := a.app.GetBoard(boardID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - if board == nil { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) - return - } - - if !hasValidReadToken { - if board.Type == model.BoardTypePrivate { - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) - return - } - } else { - if !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) - return - } - } - } - - auditRec := a.makeAuditRecord(r, "getBoard", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("boardID", boardID) - - a.logger.Debug("GetBoard", - mlog.String("boardID", boardID), - ) - - data, err := json.Marshal(board) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - // response - jsonBytesResponse(w, http.StatusOK, data) - - auditRec.Success() -} - -func (a *API) handlePatchBoard(w http.ResponseWriter, r *http.Request) { - // swagger:operation PATCH /boards/{boardID} patchBoard - // - // Partially updates a board - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // - name: Body - // in: body - // description: board patch to apply - // required: true - // schema: - // "$ref": "#/definitions/BoardPatch" - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // $ref: '#/definitions/Board' - // '404': - // description: board not found - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - boardID := mux.Vars(r)["boardID"] - board, err := a.app.GetBoard(boardID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - if board == nil { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) - return - } - - userID := getUserID(r) - - requestBody, err := ioutil.ReadAll(r.Body) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - var patch *model.BoardPatch - if err = json.Unmarshal(requestBody, &patch); err != nil { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) - return - } - - if err = patch.IsValid(); err != nil { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, err.Error(), err) - return - } - - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardProperties) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying board properties"}) - return - } - - if patch.Type != nil || patch.MinimumRole != nil { - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardType) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying board type"}) - return - } - } - if patch.ChannelID != nil { - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardRoles) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying board access"}) - return - } - } - - auditRec := a.makeAuditRecord(r, "patchBoard", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("boardID", boardID) - auditRec.AddMeta("userID", userID) - - // patch board - updatedBoard, err := a.app.PatchBoard(patch, boardID, userID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("PatchBoard", - mlog.String("boardID", boardID), - mlog.String("userID", userID), - ) - - data, err := json.Marshal(updatedBoard) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - // response - jsonBytesResponse(w, http.StatusOK, data) - - auditRec.Success() -} - -func (a *API) handleDeleteBoard(w http.ResponseWriter, r *http.Request) { - // swagger:operation DELETE /boards/{boardID} deleteBoard - // - // Removes a board - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // '404': - // description: board not found - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - boardID := mux.Vars(r)["boardID"] - userID := getUserID(r) - - // Check if board exists - board, err := a.app.GetBoard(boardID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - if board == nil { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) - return - } - - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionDeleteBoard) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to delete board"}) - return - } - - auditRec := a.makeAuditRecord(r, "deleteBoard", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("boardID", boardID) - - if err := a.app.DeleteBoard(boardID, userID); err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("DELETE Board", mlog.String("boardID", boardID)) - jsonStringResponse(w, http.StatusOK, "{}") - - auditRec.Success() -} - -func (a *API) handleDuplicateBoard(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /boards/{boardID}/duplicate duplicateBoard - // - // Returns the new created board and all the blocks - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // $ref: '#/definitions/BoardsAndBlocks' - // '404': - // description: board not found - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - boardID := mux.Vars(r)["boardID"] - userID := getUserID(r) - query := r.URL.Query() - asTemplate := query.Get("asTemplate") - toTeam := query.Get("toTeam") - - if userID == "" { - a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "", PermissionError{"access denied to board"}) - return - } - - board, err := a.app.GetBoard(boardID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - if board == nil { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) - return - } - - if toTeam == "" && !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"}) - return - } - - if toTeam != "" && !a.permissions.HasPermissionToTeam(userID, toTeam, model.PermissionViewTeam) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"}) - return - } - - if board.IsTemplate && board.Type == model.BoardTypeOpen { - if board.TeamID != model.GlobalTeamID && !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) - return - } - } else { - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) - return - } - } - - auditRec := a.makeAuditRecord(r, "duplicateBoard", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("boardID", boardID) - - a.logger.Debug("DuplicateBoard", - mlog.String("boardID", boardID), - ) - - boardsAndBlocks, _, err := a.app.DuplicateBoard(boardID, userID, toTeam, asTemplate == True) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, err.Error(), err) - return - } - - data, err := json.Marshal(boardsAndBlocks) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - // response - jsonBytesResponse(w, http.StatusOK, data) - - auditRec.Success() -} - -func (a *API) handleDuplicateBlock(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /boards/{boardID}/blocks/{blockID}/duplicate duplicateBlock - // - // Returns the new created blocks - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // - name: blockID - // in: path - // description: Block ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // type: array - // items: - // "$ref": "#/definitions/Block" - // '404': - // description: board or block not found - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - boardID := mux.Vars(r)["boardID"] - blockID := mux.Vars(r)["blockID"] - userID := getUserID(r) - query := r.URL.Query() - asTemplate := query.Get("asTemplate") - - board, err := a.app.GetBoard(boardID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - if board == nil { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) - } - - if userID == "" { - a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "", PermissionError{"access denied to board"}) - return - } - - block, err := a.app.GetBlockByID(blockID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - if block == nil { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) - return - } - - if board.ID != block.BoardID { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) - return - } - - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modify board members"}) - return - } - - auditRec := a.makeAuditRecord(r, "duplicateBlock", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("boardID", boardID) - auditRec.AddMeta("blockID", blockID) - - a.logger.Debug("DuplicateBlock", - mlog.String("boardID", boardID), - mlog.String("blockID", blockID), - ) - - blocks, err := a.app.DuplicateBlock(boardID, blockID, userID, asTemplate == True) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, err.Error(), err) - return - } - - data, err := json.Marshal(blocks) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - // response - jsonBytesResponse(w, http.StatusOK, data) - - auditRec.Success() -} - -func (a *API) handleGetBoardMetadata(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /boards/{boardID}/metadata getBoardMetadata - // - // Returns a board's metadata - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // "$ref": "#/definitions/BoardMetadata" - // '404': - // description: board not found - // '501': - // description: required license not found - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - boardID := mux.Vars(r)["boardID"] - userID := getUserID(r) - - board, boardMetadata, err := a.app.GetBoardMetadata(boardID) - if errors.Is(err, app.ErrInsufficientLicense) { - a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "", err) - return - } - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - if board == nil || boardMetadata == nil { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) - return - } - - if board.Type == model.BoardTypePrivate { - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) - return - } - } else { - if !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) - return - } - } - - auditRec := a.makeAuditRecord(r, "getBoardMetadata", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("boardID", boardID) - - data, err := json.Marshal(boardMetadata) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - // response - jsonBytesResponse(w, http.StatusOK, data) - - auditRec.Success() -} - -func (a *API) handleSearchBoards(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /teams/{teamID}/boards/search searchBoards - // - // Returns the boards that match with a search term in the team - // - // --- - // produces: - // - application/json - // parameters: - // - name: teamID - // in: path - // description: Team ID - // required: true - // type: string - // - name: q - // in: query - // description: The search term. Must have at least one character - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // type: array - // items: - // "$ref": "#/definitions/Board" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - teamID := mux.Vars(r)["teamID"] - term := r.URL.Query().Get("q") - userID := getUserID(r) - - if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"}) - return - } - - if len(term) == 0 { - jsonStringResponse(w, http.StatusOK, "[]") - return - } - - auditRec := a.makeAuditRecord(r, "searchBoards", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("teamID", teamID) - - // retrieve boards list - boards, err := a.app.SearchBoardsForUserInTeam(teamID, term, userID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("SearchBoards", - mlog.String("teamID", teamID), - mlog.Int("boardsCount", len(boards)), - ) - - data, err := json.Marshal(boards) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - // response - jsonBytesResponse(w, http.StatusOK, data) - - auditRec.AddMeta("boardsCount", len(boards)) - auditRec.Success() -} - -func (a *API) handleSearchAllBoards(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /boards/search searchBoards - // - // Returns the boards that match with a search term - // - // --- - // produces: - // - application/json - // parameters: - // - name: q - // in: query - // description: The search term. Must have at least one character - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // type: array - // items: - // "$ref": "#/definitions/Board" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - term := r.URL.Query().Get("q") - userID := getUserID(r) - - if len(term) == 0 { - jsonStringResponse(w, http.StatusOK, "[]") - return - } - - auditRec := a.makeAuditRecord(r, "searchAllBoards", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - - // retrieve boards list - boards, err := a.app.SearchBoardsForUser(term, userID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("SearchAllBoards", - mlog.Int("boardsCount", len(boards)), - ) - - data, err := json.Marshal(boards) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - // response - jsonBytesResponse(w, http.StatusOK, data) - - auditRec.AddMeta("boardsCount", len(boards)) - auditRec.Success() -} - -func (a *API) handleGetMembersForBoard(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /boards/{boardID}/members getMembersForBoard - // - // Returns the members of the board - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // type: array - // items: - // "$ref": "#/definitions/BoardMember" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - boardID := mux.Vars(r)["boardID"] - userID := getUserID(r) - - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board members"}) - return - } - - auditRec := a.makeAuditRecord(r, "getMembersForBoard", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("boardID", boardID) - - members, err := a.app.GetMembersForBoard(boardID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("GetMembersForBoard", - mlog.String("boardID", boardID), - mlog.Int("membersCount", len(members)), - ) - - data, err := json.Marshal(members) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - // response - jsonBytesResponse(w, http.StatusOK, data) - - auditRec.Success() -} - -func (a *API) handleAddMember(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /boards/{boardID}/members addMember - // - // Adds a new member to a board - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // - name: Body - // in: body - // description: membership to replace the current one with - // required: true - // schema: - // "$ref": "#/definitions/BoardMember" - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // $ref: '#/definitions/BoardMember' - // '404': - // description: board not found - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - boardID := mux.Vars(r)["boardID"] - userID := getUserID(r) - - board, err := a.app.GetBoard(boardID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - if board == nil { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) - return - } - - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardRoles) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modify board members"}) - return - } - - requestBody, err := ioutil.ReadAll(r.Body) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - var reqBoardMember *model.BoardMember - if err = json.Unmarshal(requestBody, &reqBoardMember); err != nil { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) - return - } - - if reqBoardMember.UserID == "" { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) - return - } - - // currently all memberships are created as editors by default - newBoardMember := &model.BoardMember{ - UserID: reqBoardMember.UserID, - BoardID: boardID, - SchemeEditor: true, - } - - auditRec := a.makeAuditRecord(r, "addMember", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("boardID", boardID) - auditRec.AddMeta("addedUserID", reqBoardMember.UserID) - - member, err := a.app.AddMemberToBoard(newBoardMember) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("AddMember", - mlog.String("boardID", board.ID), - mlog.String("addedUserID", reqBoardMember.UserID), - ) - - data, err := json.Marshal(member) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - // response - jsonBytesResponse(w, http.StatusOK, data) - - auditRec.Success() -} - -func (a *API) handleJoinBoard(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /boards/{boardID}/join joinBoard - // - // Become a member of a board - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // $ref: '#/definitions/BoardMember' - // '404': - // description: board not found - // '403': - // description: access denied - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - userID := getUserID(r) - if userID == "" { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil) - return - } - - boardID := mux.Vars(r)["boardID"] - board, err := a.app.GetBoard(boardID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - if board == nil { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) - return - } - if board.Type != model.BoardTypeOpen { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", nil) - return - } - - if !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", nil) - return - } - - newBoardMember := &model.BoardMember{ - UserID: userID, - BoardID: boardID, - SchemeAdmin: board.MinimumRole == model.BoardRoleAdmin, - SchemeEditor: board.MinimumRole == model.BoardRoleNone || board.MinimumRole == model.BoardRoleEditor, - SchemeCommenter: board.MinimumRole == model.BoardRoleCommenter, - SchemeViewer: board.MinimumRole == model.BoardRoleViewer, - } - - auditRec := a.makeAuditRecord(r, "joinBoard", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("boardID", boardID) - auditRec.AddMeta("addedUserID", userID) - - member, err := a.app.AddMemberToBoard(newBoardMember) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("JoinBoard", - mlog.String("boardID", board.ID), - mlog.String("addedUserID", userID), - ) - - data, err := json.Marshal(member) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - // response - jsonBytesResponse(w, http.StatusOK, data) - - auditRec.Success() -} - -func (a *API) handleLeaveBoard(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /boards/{boardID}/leave leaveBoard - // - // Remove your own membership from a board - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // '404': - // description: board not found - // '403': - // description: access denied - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - userID := getUserID(r) - if userID == "" { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil) - return - } - - boardID := mux.Vars(r)["boardID"] - - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", nil) - return - } - - board, err := a.app.GetBoard(boardID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - if board == nil { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) - return - } - - auditRec := a.makeAuditRecord(r, "leaveBoard", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("boardID", boardID) - auditRec.AddMeta("addedUserID", userID) - - err = a.app.DeleteBoardMember(boardID, userID) - if errors.Is(err, app.ErrBoardMemberIsLastAdmin) { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) - return - } - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("LeaveBoard", - mlog.String("boardID", board.ID), - mlog.String("addedUserID", userID), - ) - - jsonStringResponse(w, http.StatusOK, "{}") - - auditRec.Success() -} - -func (a *API) handleUpdateMember(w http.ResponseWriter, r *http.Request) { - // swagger:operation PUT /boards/{boardID}/members/{userID} updateMember - // - // Updates a board member - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // - name: userID - // in: path - // description: User ID - // required: true - // type: string - // - name: Body - // in: body - // description: membership to replace the current one with - // required: true - // schema: - // "$ref": "#/definitions/BoardMember" - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // $ref: '#/definitions/BoardMember' - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - boardID := mux.Vars(r)["boardID"] - paramsUserID := mux.Vars(r)["userID"] - userID := getUserID(r) - - requestBody, err := ioutil.ReadAll(r.Body) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - var reqBoardMember *model.BoardMember - if err = json.Unmarshal(requestBody, &reqBoardMember); err != nil { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) - return - } - - newBoardMember := &model.BoardMember{ - UserID: paramsUserID, - BoardID: boardID, - SchemeAdmin: reqBoardMember.SchemeAdmin, - SchemeEditor: reqBoardMember.SchemeEditor, - SchemeCommenter: reqBoardMember.SchemeCommenter, - SchemeViewer: reqBoardMember.SchemeViewer, - } - - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardRoles) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modify board members"}) - return - } - - auditRec := a.makeAuditRecord(r, "patchMember", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("boardID", boardID) - auditRec.AddMeta("patchedUserID", paramsUserID) - - member, err := a.app.UpdateBoardMember(newBoardMember) - if errors.Is(err, app.ErrBoardMemberIsLastAdmin) { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) - return - } - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("PatchMember", - mlog.String("boardID", boardID), - mlog.String("patchedUserID", paramsUserID), - ) - - data, err := json.Marshal(member) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - // response - jsonBytesResponse(w, http.StatusOK, data) - - auditRec.Success() -} - -func (a *API) handleDeleteMember(w http.ResponseWriter, r *http.Request) { - // swagger:operation DELETE /boards/{boardID}/members/{userID} deleteMember - // - // Deletes a member from a board - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // - name: userID - // in: path - // description: User ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // '404': - // description: board not found - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - boardID := mux.Vars(r)["boardID"] - paramsUserID := mux.Vars(r)["userID"] - userID := getUserID(r) - - board, err := a.app.GetBoard(boardID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - if board == nil { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", err) - return - } - - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardRoles) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modify board members"}) - return - } - - auditRec := a.makeAuditRecord(r, "deleteMember", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("boardID", boardID) - auditRec.AddMeta("addedUserID", paramsUserID) - - deleteErr := a.app.DeleteBoardMember(boardID, paramsUserID) - if errors.Is(deleteErr, app.ErrBoardMemberIsLastAdmin) { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", deleteErr) - return - } - if deleteErr != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", deleteErr) - return - } - - a.logger.Debug("DeleteMember", - mlog.String("boardID", boardID), - mlog.String("addedUserID", paramsUserID), - ) - - // response - jsonStringResponse(w, http.StatusOK, "{}") - - auditRec.Success() -} - -func (a *API) handleCreateBoardsAndBlocks(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /boards-and-blocks insertBoardsAndBlocks - // - // Creates new boards and blocks - // - // --- - // produces: - // - application/json - // parameters: - // - name: Body - // in: body - // description: the boards and blocks to create - // required: true - // schema: - // "$ref": "#/definitions/BoardsAndBlocks" - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // $ref: '#/definitions/BoardsAndBlocks' - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - userID := getUserID(r) - - requestBody, err := ioutil.ReadAll(r.Body) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - var newBab *model.BoardsAndBlocks - if err = json.Unmarshal(requestBody, &newBab); err != nil { - // a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) - return - } - - if len(newBab.Boards) == 0 { - message := "at least one board is required" - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) - return - } - - teamID := "" - boardIDs := map[string]bool{} - for _, board := range newBab.Boards { - boardIDs[board.ID] = true - - if teamID == "" { - teamID = board.TeamID - continue - } - - if teamID != board.TeamID { - message := "cannot create boards for multiple teams" - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) - return - } - - if board.ID == "" { - message := "boards need an ID to be referenced from the blocks" - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) - return - } - } - - if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board template"}) - return - } - - for _, block := range newBab.Blocks { - // Error checking - if len(block.Type) < 1 { - message := fmt.Sprintf("missing type for block id %s", block.ID) - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) - return - } - - if block.CreateAt < 1 { - message := fmt.Sprintf("invalid createAt for block id %s", block.ID) - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) - return - } - - if block.UpdateAt < 1 { - message := fmt.Sprintf("invalid UpdateAt for block id %s", block.ID) - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) - return - } - - if !boardIDs[block.BoardID] { - message := fmt.Sprintf("invalid BoardID %s (not exists in the created boards)", block.BoardID) - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) - return - } - } - - // IDs of boards and blocks are used to confirm that they're - // linked and then regenerated by the server - newBab, err = model.GenerateBoardsAndBlocksIDs(newBab, a.logger) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, err.Error(), err) - return - } - - auditRec := a.makeAuditRecord(r, "createBoardsAndBlocks", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("teamID", teamID) - auditRec.AddMeta("userID", userID) - auditRec.AddMeta("boardsCount", len(newBab.Boards)) - auditRec.AddMeta("blocksCount", len(newBab.Blocks)) - - // create boards and blocks - bab, err := a.app.CreateBoardsAndBlocks(newBab, userID, true) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, err.Error(), err) - return - } - - a.logger.Debug("CreateBoardsAndBlocks", - mlog.String("teamID", teamID), - mlog.String("userID", userID), - mlog.Int("boardCount", len(bab.Boards)), - mlog.Int("blockCount", len(bab.Blocks)), - ) - - data, err := json.Marshal(bab) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, err.Error(), err) - return - } - - // response - jsonBytesResponse(w, http.StatusOK, data) - - auditRec.Success() -} - -func (a *API) handlePatchBoardsAndBlocks(w http.ResponseWriter, r *http.Request) { - // swagger:operation PATCH /boards-and-blocks patchBoardsAndBlocks - // - // Patches a set of related boards and blocks - // - // --- - // produces: - // - application/json - // parameters: - // - name: Body - // in: body - // description: the patches for the boards and blocks - // required: true - // schema: - // "$ref": "#/definitions/PatchBoardsAndBlocks" - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // $ref: '#/definitions/BoardsAndBlocks' - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - userID := getUserID(r) - - requestBody, err := ioutil.ReadAll(r.Body) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - var pbab *model.PatchBoardsAndBlocks - if err = json.Unmarshal(requestBody, &pbab); err != nil { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) - return - } - - if err = pbab.IsValid(); err != nil { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) - return - } - - teamID := "" - boardIDMap := map[string]bool{} - for i, boardID := range pbab.BoardIDs { - boardIDMap[boardID] = true - patch := pbab.BoardPatches[i] - - if err = patch.IsValid(); err != nil { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) - return - } - - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardProperties) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying board properties"}) - return - } - - if patch.Type != nil || patch.MinimumRole != nil { - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardType) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying board type"}) - return - } - } - - board, err2 := a.app.GetBoard(boardID) - if err2 != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err2) - return - } - if board == nil { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil) - return - } - - if teamID == "" { - teamID = board.TeamID - } - if teamID != board.TeamID { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil) - return - } - } - - for _, blockID := range pbab.BlockIDs { - block, err2 := a.app.GetBlockByID(blockID) - if err2 != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err2) - return - } - if block == nil { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil) - return - } - - if _, ok := boardIDMap[block.BoardID]; !ok { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil) - return - } - - if !a.permissions.HasPermissionToBoard(userID, block.BoardID, model.PermissionManageBoardCards) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying cards"}) - return - } - } - - auditRec := a.makeAuditRecord(r, "patchBoardsAndBlocks", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("boardsCount", len(pbab.BoardIDs)) - auditRec.AddMeta("blocksCount", len(pbab.BlockIDs)) - - bab, err := a.app.PatchBoardsAndBlocks(pbab, userID) - if errors.Is(err, app.ErrPatchUpdatesLimitedCards) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", err) - return - } - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("PATCH BoardsAndBlocks", - mlog.Int("boardsCount", len(pbab.BoardIDs)), - mlog.Int("blocksCount", len(pbab.BlockIDs)), - ) - - data, err := json.Marshal(bab) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - // response - jsonBytesResponse(w, http.StatusOK, data) - - auditRec.Success() -} - -func (a *API) handleDeleteBoardsAndBlocks(w http.ResponseWriter, r *http.Request) { - // swagger:operation DELETE /boards-and-blocks deleteBoardsAndBlocks - // - // Deletes boards and blocks - // - // --- - // produces: - // - application/json - // parameters: - // - name: Body - // in: body - // description: the boards and blocks to delete - // required: true - // schema: - // "$ref": "#/definitions/DeleteBoardsAndBlocks" - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - userID := getUserID(r) - - requestBody, err := ioutil.ReadAll(r.Body) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - var dbab *model.DeleteBoardsAndBlocks - if err = json.Unmarshal(requestBody, &dbab); err != nil { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) - return - } - - // user must have permission to delete all the boards, and that - // would include the permission to manage their blocks - teamID := "" - boardIDMap := map[string]bool{} - for _, boardID := range dbab.Boards { - boardIDMap[boardID] = true - // all boards in the request should belong to the same team - board, err := a.app.GetBoard(boardID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - if board == nil { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) - return - } - if teamID == "" { - teamID = board.TeamID - } - if teamID != board.TeamID { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil) - return - } - - // permission check - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionDeleteBoard) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to delete board"}) - return - } - } - - for _, blockID := range dbab.Blocks { - block, err2 := a.app.GetBlockByID(blockID) - if err2 != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err2) - return - } - if block == nil { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil) - return - } - - if _, ok := boardIDMap[block.BoardID]; !ok { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil) - return - } - - if !a.permissions.HasPermissionToBoard(userID, block.BoardID, model.PermissionManageBoardCards) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying cards"}) - return - } - } - - if err := dbab.IsValid(); err != nil { - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) - return - } - - auditRec := a.makeAuditRecord(r, "deleteBoardsAndBlocks", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("boardsCount", len(dbab.Boards)) - auditRec.AddMeta("blocksCount", len(dbab.Blocks)) - - if err := a.app.DeleteBoardsAndBlocks(dbab, userID); err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("DELETE BoardsAndBlocks", - mlog.Int("boardsCount", len(dbab.Boards)), - mlog.Int("blocksCount", len(dbab.Blocks)), - ) - - // response - jsonStringResponse(w, http.StatusOK, "{}") - auditRec.Success() -} - -func (a *API) handleCloudLimits(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /limits cloudLimits - // - // Fetches the cloud limits of the server. - // - // --- - // produces: - // - application/json - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // "$ref": "#/definitions/BoardsCloudLimits" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - boardsCloudLimits, err := a.app.GetBoardsCloudLimits() - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - data, err := json.Marshal(boardsCloudLimits) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, data) -} - -func (a *API) handleHello(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /hello hello - // - // Responds with `Hello` if the web service is running. - // - // --- - // produces: - // - text/plain - // responses: - // '200': - // description: success - stringResponse(w, "Hello") -} - -func (a *API) handleNotifyAdminUpgrade(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /api/v2/teams/{teamID}/notifyadminupgrade handleNotifyAdminUpgrade - // - // Notifies admins for upgrade request. - // - // --- - // produces: - // - application/json - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - if !a.MattermostAuth { - a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", errAPINotSupportedInStandaloneMode) - return - } - - vars := mux.Vars(r) - teamID := vars["teamID"] - - if err := a.app.NotifyPortalAdminsUpgradeRequest(teamID); err != nil { - jsonStringResponse(w, http.StatusOK, "{}") - } -} - // Response helpers func (a *API) errorResponse(w http.ResponseWriter, api string, code int, message string, sourceError error) { @@ -4529,7 +184,7 @@ func (a *API) errorResponse(w http.ResponseWriter, api string, code int, message ) } - w.Header().Set("Content-Type", "application/json") + setResponseHeader(w, "Content-Type", "application/json") data, err := json.Marshal(model.ErrorResponse{Error: message, ErrorCode: code}) if err != nil { data = []byte("{}") @@ -4539,18 +194,26 @@ func (a *API) errorResponse(w http.ResponseWriter, api string, code int, message } func stringResponse(w http.ResponseWriter, message string) { - w.Header().Set("Content-Type", "text/plain") + setResponseHeader(w, "Content-Type", "text/plain") _, _ = fmt.Fprint(w, message) } func jsonStringResponse(w http.ResponseWriter, code int, message string) { - w.Header().Set("Content-Type", "application/json") + setResponseHeader(w, "Content-Type", "application/json") w.WriteHeader(code) fmt.Fprint(w, message) } func jsonBytesResponse(w http.ResponseWriter, code int, json []byte) { - w.Header().Set("Content-Type", "application/json") + setResponseHeader(w, "Content-Type", "application/json") w.WriteHeader(code) _, _ = w.Write(json) } + +func setResponseHeader(w http.ResponseWriter, key string, value string) { + header := w.Header() + if header == nil { + return + } + header.Set(key, value) +} diff --git a/server/api/archive.go b/server/api/archive.go index 8b63b14d8..f859d811d 100644 --- a/server/api/archive.go +++ b/server/api/archive.go @@ -16,6 +16,13 @@ const ( archiveExtension = ".boardarchive" ) +func (a *API) registerAchivesRoutes(r *mux.Router) { + // Archive APIs + r.HandleFunc("/boards/{boardID}/archive/export", a.sessionRequired(a.handleArchiveExportBoard)).Methods("GET") + r.HandleFunc("/teams/{teamID}/archive/import", a.sessionRequired(a.handleArchiveImport)).Methods("POST") + r.HandleFunc("/teams/{teamID}/archive/export", a.sessionRequired(a.handleArchiveExportTeam)).Methods("GET") +} + func (a *API) handleArchiveExportBoard(w http.ResponseWriter, r *http.Request) { // swagger:operation GET /boards/{boardID}/archive/export archiveExportBoard // @@ -84,75 +91,6 @@ func (a *API) handleArchiveExportBoard(w http.ResponseWriter, r *http.Request) { auditRec.Success() } -func (a *API) handleArchiveExportTeam(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /teams/{teamID}/archive/export archiveExportTeam - // - // Exports an archive of all blocks for all the boards in a team. - // - // --- - // produces: - // - application/json - // parameters: - // - name: teamID - // in: path - // description: Id of team - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // content: - // application-octet-stream: - // type: string - // format: binary - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - if a.MattermostAuth { - a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil) - } - - vars := mux.Vars(r) - teamID := vars["teamID"] - - ctx := r.Context() - session, _ := ctx.Value(sessionContextKey).(*model.Session) - userID := session.UserID - - auditRec := a.makeAuditRecord(r, "archiveExportTeam", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("TeamID", teamID) - - boards, err := a.app.GetBoardsForUserAndTeam(userID, teamID) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - ids := []string{} - for _, board := range boards { - ids = append(ids, board.ID) - } - - opts := model.ExportArchiveOptions{ - TeamID: teamID, - BoardIDs: ids, - } - - filename := fmt.Sprintf("archive-%s%s", time.Now().Format("2006-01-02"), archiveExtension) - w.Header().Set("Content-Type", "application/octet-stream") - w.Header().Set("Content-Disposition", "attachment; filename="+filename) - w.Header().Set("Content-Transfer-Encoding", "binary") - - if err := a.app.ExportArchive(w, opts); err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - } - - auditRec.Success() -} - func (a *API) handleArchiveImport(w http.ResponseWriter, r *http.Request) { // swagger:operation POST /teams/{teamID}/archive/import archiveImport // @@ -225,3 +163,72 @@ func (a *API) handleArchiveImport(w http.ResponseWriter, r *http.Request) { jsonStringResponse(w, http.StatusOK, "{}") auditRec.Success() } + +func (a *API) handleArchiveExportTeam(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /teams/{teamID}/archive/export archiveExportTeam + // + // Exports an archive of all blocks for all the boards in a team. + // + // --- + // produces: + // - application/json + // parameters: + // - name: teamID + // in: path + // description: Id of team + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // content: + // application-octet-stream: + // type: string + // format: binary + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + if a.MattermostAuth { + a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil) + } + + vars := mux.Vars(r) + teamID := vars["teamID"] + + ctx := r.Context() + session, _ := ctx.Value(sessionContextKey).(*model.Session) + userID := session.UserID + + auditRec := a.makeAuditRecord(r, "archiveExportTeam", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + auditRec.AddMeta("TeamID", teamID) + + boards, err := a.app.GetBoardsForUserAndTeam(userID, teamID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + ids := []string{} + for _, board := range boards { + ids = append(ids, board.ID) + } + + opts := model.ExportArchiveOptions{ + TeamID: teamID, + BoardIDs: ids, + } + + filename := fmt.Sprintf("archive-%s%s", time.Now().Format("2006-01-02"), archiveExtension) + w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("Content-Disposition", "attachment; filename="+filename) + w.Header().Set("Content-Transfer-Encoding", "binary") + + if err := a.app.ExportArchive(w, opts); err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + } + + auditRec.Success() +} diff --git a/server/api/auth.go b/server/api/auth.go index 2df40e162..82ca9f711 100644 --- a/server/api/auth.go +++ b/server/api/auth.go @@ -3,8 +3,6 @@ package api import ( "context" "encoding/json" - "fmt" - "io" "io/ioutil" "net" "net/http" @@ -19,123 +17,15 @@ import ( "github.com/mattermost/mattermost-server/v6/shared/mlog" ) -const ( - MinimumPasswordLength = 8 -) - -type ParamError struct { - msg string -} - -func (pe ParamError) Error() string { - return pe.msg -} - -// LoginRequest is a login request -// swagger:model -type LoginRequest struct { - // Type of login, currently must be set to "normal" - // required: true - Type string `json:"type"` - - // If specified, login using username - // required: false - Username string `json:"username"` - - // If specified, login using email - // required: false - Email string `json:"email"` - - // Password - // required: true - Password string `json:"password"` - - // MFA token - // required: false - // swagger:ignore - MfaToken string `json:"mfa_token"` -} - -// LoginResponse is a login response -// swagger:model -type LoginResponse struct { - // Session token - // required: true - Token string `json:"token"` -} - -func LoginResponseFromJSON(data io.Reader) (*LoginResponse, error) { - var resp LoginResponse - if err := json.NewDecoder(data).Decode(&resp); err != nil { - return nil, err +func (a *API) registerAuthRoutes(r *mux.Router) { + // personal-server specific routes. These are not needed in plugin mode. + if !a.isPlugin { + r.HandleFunc("/login", a.handleLogin).Methods("POST") + r.HandleFunc("/logout", a.sessionRequired(a.handleLogout)).Methods("POST") + r.HandleFunc("/register", a.handleRegister).Methods("POST") + r.HandleFunc("/teams/{teamID}/regenerate_signup_token", a.sessionRequired(a.handlePostTeamRegenerateSignupToken)).Methods("POST") + r.HandleFunc("/users/{userID}/changepassword", a.sessionRequired(a.handleChangePassword)).Methods("POST") } - return &resp, nil -} - -// RegisterRequest is a user registration request -// swagger:model -type RegisterRequest struct { - // User name - // required: true - Username string `json:"username"` - - // User's email - // required: true - Email string `json:"email"` - - // Password - // required: true - Password string `json:"password"` - - // Registration authorization token - // required: true - Token string `json:"token"` -} - -func (rd *RegisterRequest) IsValid() error { - if strings.TrimSpace(rd.Username) == "" { - return ParamError{"username is required"} - } - if strings.TrimSpace(rd.Email) == "" { - return ParamError{"email is required"} - } - if !auth.IsEmailValid(rd.Email) { - return ParamError{"invalid email format"} - } - if rd.Password == "" { - return ParamError{"password is required"} - } - return isValidPassword(rd.Password) -} - -// ChangePasswordRequest is a user password change request -// swagger:model -type ChangePasswordRequest struct { - // Old password - // required: true - OldPassword string `json:"oldPassword"` - - // New password - // required: true - NewPassword string `json:"newPassword"` -} - -// IsValid validates a password change request. -func (rd *ChangePasswordRequest) IsValid() error { - if rd.OldPassword == "" { - return ParamError{"old password is required"} - } - if rd.NewPassword == "" { - return ParamError{"new password is required"} - } - return isValidPassword(rd.NewPassword) -} - -func isValidPassword(password string) error { - if len(password) < MinimumPasswordLength { - return ParamError{fmt.Sprintf("password must be at least %d characters", MinimumPasswordLength)} - } - return nil } func (a *API) handleLogin(w http.ResponseWriter, r *http.Request) { @@ -187,7 +77,7 @@ func (a *API) handleLogin(w http.ResponseWriter, r *http.Request) { return } - var loginData LoginRequest + var loginData model.LoginRequest err = json.Unmarshal(requestBody, &loginData) if err != nil { a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) @@ -205,7 +95,7 @@ func (a *API) handleLogin(w http.ResponseWriter, r *http.Request) { a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "incorrect login", err) return } - json, err := json.Marshal(LoginResponse{Token: token}) + json, err := json.Marshal(model.LoginResponse{Token: token}) if err != nil { a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) return @@ -315,7 +205,7 @@ func (a *API) handleRegister(w http.ResponseWriter, r *http.Request) { return } - var registerData RegisterRequest + var registerData model.RegisterRequest err = json.Unmarshal(requestBody, ®isterData) if err != nil { a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) @@ -425,7 +315,7 @@ func (a *API) handleChangePassword(w http.ResponseWriter, r *http.Request) { return } - var requestData ChangePasswordRequest + var requestData model.ChangePasswordRequest if err = json.Unmarshal(requestBody, &requestData); err != nil { a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) return diff --git a/server/api/blocks.go b/server/api/blocks.go new file mode 100644 index 000000000..162d05787 --- /dev/null +++ b/server/api/blocks.go @@ -0,0 +1,758 @@ +package api + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "strconv" + + "github.com/gorilla/mux" + "github.com/mattermost/focalboard/server/app" + "github.com/mattermost/focalboard/server/model" + "github.com/mattermost/focalboard/server/services/audit" + + "github.com/mattermost/mattermost-server/v6/shared/mlog" +) + +func (a *API) registerBlocksRoutes(r *mux.Router) { + // Blocks APIs + r.HandleFunc("/boards/{boardID}/blocks", a.attachSession(a.handleGetBlocks, false)).Methods("GET") + r.HandleFunc("/boards/{boardID}/blocks", a.sessionRequired(a.handlePostBlocks)).Methods("POST") + r.HandleFunc("/boards/{boardID}/blocks", a.sessionRequired(a.handlePatchBlocks)).Methods("PATCH") + r.HandleFunc("/boards/{boardID}/blocks/{blockID}", a.sessionRequired(a.handleDeleteBlock)).Methods("DELETE") + r.HandleFunc("/boards/{boardID}/blocks/{blockID}", a.sessionRequired(a.handlePatchBlock)).Methods("PATCH") + r.HandleFunc("/boards/{boardID}/blocks/{blockID}/undelete", a.sessionRequired(a.handleUndeleteBlock)).Methods("POST") + r.HandleFunc("/boards/{boardID}/blocks/{blockID}/duplicate", a.sessionRequired(a.handleDuplicateBlock)).Methods("POST") +} + +func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /boards/{boardID}/blocks getBlocks + // + // Returns blocks + // + // --- + // produces: + // - application/json + // parameters: + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // - name: parent_id + // in: query + // description: ID of parent block, omit to specify all blocks + // required: false + // type: string + // - name: type + // in: query + // description: Type of blocks to return, omit to specify all types + // required: false + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // type: array + // items: + // "$ref": "#/definitions/Block" + // '404': + // description: board not found + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + query := r.URL.Query() + parentID := query.Get("parent_id") + blockType := query.Get("type") + all := query.Get("all") + blockID := query.Get("block_id") + boardID := mux.Vars(r)["boardID"] + + userID := getUserID(r) + + hasValidReadToken := a.hasValidReadTokenForBoard(r, boardID) + if userID == "" && !hasValidReadToken { + a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "", PermissionError{"access denied to board"}) + return + } + + board, err := a.app.GetBoard(boardID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + if board == nil { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "Board not found", nil) + return + } + + if !hasValidReadToken { + if board.IsTemplate && board.Type == model.BoardTypeOpen { + if board.TeamID != model.GlobalTeamID && !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board template"}) + return + } + } else { + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) + return + } + } + } + + auditRec := a.makeAuditRecord(r, "getBlocks", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + auditRec.AddMeta("boardID", boardID) + auditRec.AddMeta("parentID", parentID) + auditRec.AddMeta("blockType", blockType) + auditRec.AddMeta("all", all) + auditRec.AddMeta("blockID", blockID) + + var blocks []model.Block + var block *model.Block + switch { + case all != "": + blocks, err = a.app.GetBlocksForBoard(boardID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + case blockID != "": + block, err = a.app.GetBlockByID(blockID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + if block != nil { + if block.BoardID != boardID { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) + return + } + + blocks = append(blocks, *block) + } + default: + blocks, err = a.app.GetBlocks(boardID, parentID, blockType) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + } + + a.logger.Debug("GetBlocks", + mlog.String("boardID", boardID), + mlog.String("parentID", parentID), + mlog.String("blockType", blockType), + mlog.String("blockID", blockID), + mlog.Int("block_count", len(blocks)), + ) + + var bErr error + blocks, bErr = a.app.ApplyCloudLimits(blocks) + if bErr != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", bErr) + return + } + + json, err := json.Marshal(blocks) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + jsonBytesResponse(w, http.StatusOK, json) + + auditRec.AddMeta("blockCount", len(blocks)) + auditRec.Success() +} + +func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) { + // swagger:operation POST /boards/{boardID}/blocks updateBlocks + // + // Insert blocks. The specified IDs will only be used to link + // blocks with existing ones, the rest will be replaced by server + // generated IDs + // + // --- + // produces: + // - application/json + // parameters: + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // - name: disable_notify + // in: query + // description: Disables notifications (for bulk data inserting) + // required: false + // type: bool + // - name: Body + // in: body + // description: array of blocks to insert or update + // required: true + // schema: + // type: array + // items: + // "$ref": "#/definitions/Block" + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // items: + // $ref: '#/definitions/Block' + // type: array + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + boardID := mux.Vars(r)["boardID"] + userID := getUserID(r) + + val := r.URL.Query().Get("disable_notify") + disableNotify := val == True + + // in phase 1 we use "manage_board_cards", but we would have to + // check on specific actions for phase 2 + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"}) + return + } + + requestBody, err := ioutil.ReadAll(r.Body) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + var blocks []model.Block + + err = json.Unmarshal(requestBody, &blocks) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + for _, block := range blocks { + // Error checking + if len(block.Type) < 1 { + message := fmt.Sprintf("missing type for block id %s", block.ID) + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) + return + } + + if block.CreateAt < 1 { + message := fmt.Sprintf("invalid createAt for block id %s", block.ID) + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) + return + } + + if block.UpdateAt < 1 { + message := fmt.Sprintf("invalid UpdateAt for block id %s", block.ID) + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) + return + } + + if block.BoardID != boardID { + message := fmt.Sprintf("invalid BoardID for block id %s", block.ID) + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) + return + } + } + + blocks = model.GenerateBlockIDs(blocks, a.logger) + + auditRec := a.makeAuditRecord(r, "postBlocks", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + auditRec.AddMeta("disable_notify", disableNotify) + + ctx := r.Context() + session := ctx.Value(sessionContextKey).(*model.Session) + + model.StampModificationMetadata(userID, blocks, auditRec) + + // this query param exists when creating template from board, or board from template + sourceBoardID := r.URL.Query().Get("sourceBoardID") + if sourceBoardID != "" { + if updateFileIDsErr := a.app.CopyCardFiles(sourceBoardID, blocks); updateFileIDsErr != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", updateFileIDsErr) + return + } + } + + newBlocks, err := a.app.InsertBlocks(blocks, session.UserID, !disableNotify) + if err != nil { + if errors.Is(err, app.ErrViewsLimitReached) { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, err.Error(), err) + } else { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + } + + return + } + + a.logger.Debug("POST Blocks", + mlog.Int("block_count", len(blocks)), + mlog.Bool("disable_notify", disableNotify), + ) + + json, err := json.Marshal(newBlocks) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + jsonBytesResponse(w, http.StatusOK, json) + + auditRec.AddMeta("blockCount", len(blocks)) + auditRec.Success() +} + +func (a *API) handleDeleteBlock(w http.ResponseWriter, r *http.Request) { + // swagger:operation DELETE /boards/{boardID}/blocks/{blockID} deleteBlock + // + // Deletes a block + // + // --- + // produces: + // - application/json + // parameters: + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // - name: blockID + // in: path + // description: ID of block to delete + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // '404': + // description: block not found + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + userID := getUserID(r) + vars := mux.Vars(r) + boardID := vars["boardID"] + blockID := vars["blockID"] + + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"}) + return + } + + block, err := a.app.GetBlockByID(blockID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + if block == nil || block.BoardID != boardID { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) + return + } + + auditRec := a.makeAuditRecord(r, "deleteBlock", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + auditRec.AddMeta("boardID", boardID) + auditRec.AddMeta("blockID", blockID) + + err = a.app.DeleteBlock(blockID, userID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("DELETE Block", mlog.String("boardID", boardID), mlog.String("blockID", blockID)) + jsonStringResponse(w, http.StatusOK, "{}") + + auditRec.Success() +} + +func (a *API) handleUndeleteBlock(w http.ResponseWriter, r *http.Request) { + // swagger:operation POST /boards/{boardID}/blocks/{blockID}/undelete undeleteBlock + // + // Undeletes a block + // + // --- + // produces: + // - application/json + // parameters: + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // - name: blockID + // in: path + // description: ID of block to undelete + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // "$ref": "#/definitions/BlockPatch" + // '404': + // description: block not found + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + ctx := r.Context() + session := ctx.Value(sessionContextKey).(*model.Session) + userID := session.UserID + + vars := mux.Vars(r) + blockID := vars["blockID"] + boardID := vars["boardID"] + + board, err := a.app.GetBoard(boardID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + if board == nil { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) + return + } + + block, err := a.app.GetLastBlockHistoryEntry(blockID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + if block == nil { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) + return + } + + if board.ID != block.BoardID { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) + return + } + + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modify board members"}) + return + } + + auditRec := a.makeAuditRecord(r, "undeleteBlock", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + auditRec.AddMeta("blockID", blockID) + + undeletedBlock, err := a.app.UndeleteBlock(blockID, userID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + undeletedBlockData, err := json.Marshal(undeletedBlock) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("UNDELETE Block", mlog.String("blockID", blockID)) + jsonBytesResponse(w, http.StatusOK, undeletedBlockData) + + auditRec.Success() +} + +func (a *API) handlePatchBlock(w http.ResponseWriter, r *http.Request) { + // swagger:operation PATCH /boards/{boardID}/blocks/{blockID} patchBlock + // + // Partially updates a block + // + // --- + // produces: + // - application/json + // parameters: + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // - name: blockID + // in: path + // description: ID of block to patch + // required: true + // type: string + // - name: Body + // in: body + // description: block patch to apply + // required: true + // schema: + // "$ref": "#/definitions/BlockPatch" + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // '404': + // description: block not found + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + userID := getUserID(r) + vars := mux.Vars(r) + boardID := vars["boardID"] + blockID := vars["blockID"] + + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"}) + return + } + + block, err := a.app.GetBlockByID(blockID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + if block == nil || block.BoardID != boardID { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) + return + } + + requestBody, err := ioutil.ReadAll(r.Body) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + var patch *model.BlockPatch + err = json.Unmarshal(requestBody, &patch) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + auditRec := a.makeAuditRecord(r, "patchBlock", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + auditRec.AddMeta("boardID", boardID) + auditRec.AddMeta("blockID", blockID) + + err = a.app.PatchBlock(blockID, patch, userID) + if errors.Is(err, app.ErrPatchUpdatesLimitedCards) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", err) + return + } + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("PATCH Block", mlog.String("boardID", boardID), mlog.String("blockID", blockID)) + jsonStringResponse(w, http.StatusOK, "{}") + + auditRec.Success() +} + +func (a *API) handlePatchBlocks(w http.ResponseWriter, r *http.Request) { + // swagger:operation PATCH /boards/{boardID}/blocks/ patchBlocks + // + // Partially updates batch of blocks + // + // --- + // produces: + // - application/json + // parameters: + // - name: boardID + // in: path + // description: Workspace ID + // required: true + // type: string + // - name: Body + // in: body + // description: block Ids and block patches to apply + // required: true + // schema: + // "$ref": "#/definitions/BlockPatchBatch" + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + ctx := r.Context() + session := ctx.Value(sessionContextKey).(*model.Session) + userID := session.UserID + + vars := mux.Vars(r) + teamID := vars["teamID"] + + requestBody, err := ioutil.ReadAll(r.Body) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + var patches *model.BlockPatchBatch + err = json.Unmarshal(requestBody, &patches) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + auditRec := a.makeAuditRecord(r, "patchBlocks", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + for i := range patches.BlockIDs { + auditRec.AddMeta("block_"+strconv.FormatInt(int64(i), 10), patches.BlockIDs[i]) + } + + for _, blockID := range patches.BlockIDs { + var block *model.Block + block, err = a.app.GetBlockByID(blockID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"}) + return + } + if !a.permissions.HasPermissionToBoard(userID, block.BoardID, model.PermissionManageBoardCards) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"}) + return + } + } + + err = a.app.PatchBlocks(teamID, patches, userID) + if errors.Is(err, app.ErrPatchUpdatesLimitedCards) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", err) + return + } + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("PATCH Blocks", mlog.String("patches", strconv.Itoa(len(patches.BlockIDs)))) + jsonStringResponse(w, http.StatusOK, "{}") + + auditRec.Success() +} + +func (a *API) handleDuplicateBlock(w http.ResponseWriter, r *http.Request) { + // swagger:operation POST /boards/{boardID}/blocks/{blockID}/duplicate duplicateBlock + // + // Returns the new created blocks + // + // --- + // produces: + // - application/json + // parameters: + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // - name: blockID + // in: path + // description: Block ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // type: array + // items: + // "$ref": "#/definitions/Block" + // '404': + // description: board or block not found + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + boardID := mux.Vars(r)["boardID"] + blockID := mux.Vars(r)["blockID"] + userID := getUserID(r) + query := r.URL.Query() + asTemplate := query.Get("asTemplate") + + board, err := a.app.GetBoard(boardID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + if board == nil { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) + } + + if userID == "" { + a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "", PermissionError{"access denied to board"}) + return + } + + block, err := a.app.GetBlockByID(blockID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + if block == nil { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) + return + } + + if board.ID != block.BoardID { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) + return + } + + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modify board members"}) + return + } + + auditRec := a.makeAuditRecord(r, "duplicateBlock", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + auditRec.AddMeta("boardID", boardID) + auditRec.AddMeta("blockID", blockID) + + a.logger.Debug("DuplicateBlock", + mlog.String("boardID", boardID), + mlog.String("blockID", blockID), + ) + + blocks, err := a.app.DuplicateBlock(boardID, blockID, userID, asTemplate == True) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, err.Error(), err) + return + } + + data, err := json.Marshal(blocks) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + // response + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.Success() +} diff --git a/server/api/boards.go b/server/api/boards.go new file mode 100644 index 000000000..df2e335bf --- /dev/null +++ b/server/api/boards.go @@ -0,0 +1,656 @@ +package api + +import ( + "encoding/json" + "errors" + "io/ioutil" + "net/http" + + "github.com/gorilla/mux" + "github.com/mattermost/focalboard/server/app" + "github.com/mattermost/focalboard/server/model" + "github.com/mattermost/focalboard/server/services/audit" + + "github.com/mattermost/mattermost-server/v6/shared/mlog" +) + +func (a *API) registerBoardsRoutes(r *mux.Router) { + r.HandleFunc("/teams/{teamID}/boards", a.sessionRequired(a.handleGetBoards)).Methods("GET") + r.HandleFunc("/boards", a.sessionRequired(a.handleCreateBoard)).Methods("POST") + r.HandleFunc("/boards/{boardID}", a.attachSession(a.handleGetBoard, false)).Methods("GET") + r.HandleFunc("/boards/{boardID}", a.sessionRequired(a.handlePatchBoard)).Methods("PATCH") + r.HandleFunc("/boards/{boardID}", a.sessionRequired(a.handleDeleteBoard)).Methods("DELETE") + r.HandleFunc("/boards/{boardID}/duplicate", a.sessionRequired(a.handleDuplicateBoard)).Methods("POST") + r.HandleFunc("/boards/{boardID}/undelete", a.sessionRequired(a.handleUndeleteBoard)).Methods("POST") + r.HandleFunc("/boards/{boardID}/metadata", a.sessionRequired(a.handleGetBoardMetadata)).Methods("GET") +} + +func (a *API) handleGetBoards(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /teams/{teamID}/boards getBoards + // + // Returns team boards + // + // --- + // produces: + // - application/json + // parameters: + // - name: teamID + // in: path + // description: Team ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // type: array + // items: + // "$ref": "#/definitions/Board" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + teamID := mux.Vars(r)["teamID"] + userID := getUserID(r) + + if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"}) + return + } + + auditRec := a.makeAuditRecord(r, "getBoards", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + auditRec.AddMeta("teamID", teamID) + + // retrieve boards list + boards, err := a.app.GetBoardsForUserAndTeam(userID, teamID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("GetBoards", + mlog.String("teamID", teamID), + mlog.Int("boardsCount", len(boards)), + ) + + data, err := json.Marshal(boards) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + // response + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.AddMeta("boardsCount", len(boards)) + auditRec.Success() +} + +func (a *API) handleCreateBoard(w http.ResponseWriter, r *http.Request) { + // swagger:operation POST /boards createBoard + // + // Creates a new board + // + // --- + // produces: + // - application/json + // parameters: + // - name: Body + // in: body + // description: the board to create + // required: true + // schema: + // "$ref": "#/definitions/Board" + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // $ref: '#/definitions/Board' + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + userID := getUserID(r) + + requestBody, err := ioutil.ReadAll(r.Body) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + var newBoard *model.Board + if err = json.Unmarshal(requestBody, &newBoard); err != nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) + return + } + + if newBoard.Type == model.BoardTypeOpen { + if !a.permissions.HasPermissionToTeam(userID, newBoard.TeamID, model.PermissionCreatePublicChannel) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to create public boards"}) + return + } + } else { + if !a.permissions.HasPermissionToTeam(userID, newBoard.TeamID, model.PermissionCreatePrivateChannel) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to create private boards"}) + return + } + } + + if err = newBoard.IsValid(); err != nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, err.Error(), err) + return + } + + auditRec := a.makeAuditRecord(r, "createBoard", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + auditRec.AddMeta("teamID", newBoard.TeamID) + auditRec.AddMeta("boardType", newBoard.Type) + + // create board + board, err := a.app.CreateBoard(newBoard, userID, true) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("CreateBoard", + mlog.String("teamID", board.TeamID), + mlog.String("boardID", board.ID), + mlog.String("boardType", string(board.Type)), + mlog.String("userID", userID), + ) + + data, err := json.Marshal(board) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + // response + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.Success() +} + +func (a *API) handleGetBoard(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /boards/{boardID} getBoard + // + // Returns a board + // + // --- + // produces: + // - application/json + // parameters: + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // "$ref": "#/definitions/Board" + // '404': + // description: board not found + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + boardID := mux.Vars(r)["boardID"] + userID := getUserID(r) + + hasValidReadToken := a.hasValidReadTokenForBoard(r, boardID) + if userID == "" && !hasValidReadToken { + a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "", PermissionError{"access denied to board"}) + return + } + + board, err := a.app.GetBoard(boardID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + if board == nil { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) + return + } + + if !hasValidReadToken { + if board.Type == model.BoardTypePrivate { + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) + return + } + } else { + if !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) + return + } + } + } + + auditRec := a.makeAuditRecord(r, "getBoard", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + auditRec.AddMeta("boardID", boardID) + + a.logger.Debug("GetBoard", + mlog.String("boardID", boardID), + ) + + data, err := json.Marshal(board) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + // response + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.Success() +} + +func (a *API) handlePatchBoard(w http.ResponseWriter, r *http.Request) { + // swagger:operation PATCH /boards/{boardID} patchBoard + // + // Partially updates a board + // + // --- + // produces: + // - application/json + // parameters: + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // - name: Body + // in: body + // description: board patch to apply + // required: true + // schema: + // "$ref": "#/definitions/BoardPatch" + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // $ref: '#/definitions/Board' + // '404': + // description: board not found + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + boardID := mux.Vars(r)["boardID"] + board, err := a.app.GetBoard(boardID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + if board == nil { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) + return + } + + userID := getUserID(r) + + requestBody, err := ioutil.ReadAll(r.Body) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + var patch *model.BoardPatch + if err = json.Unmarshal(requestBody, &patch); err != nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) + return + } + + if err = patch.IsValid(); err != nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, err.Error(), err) + return + } + + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardProperties) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying board properties"}) + return + } + + if patch.Type != nil || patch.MinimumRole != nil { + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardType) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying board type"}) + return + } + } + if patch.ChannelID != nil { + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardRoles) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying board access"}) + return + } + } + + auditRec := a.makeAuditRecord(r, "patchBoard", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + auditRec.AddMeta("boardID", boardID) + auditRec.AddMeta("userID", userID) + + // patch board + updatedBoard, err := a.app.PatchBoard(patch, boardID, userID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("PatchBoard", + mlog.String("boardID", boardID), + mlog.String("userID", userID), + ) + + data, err := json.Marshal(updatedBoard) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + // response + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.Success() +} + +func (a *API) handleDeleteBoard(w http.ResponseWriter, r *http.Request) { + // swagger:operation DELETE /boards/{boardID} deleteBoard + // + // Removes a board + // + // --- + // produces: + // - application/json + // parameters: + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // '404': + // description: board not found + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + boardID := mux.Vars(r)["boardID"] + userID := getUserID(r) + + // Check if board exists + board, err := a.app.GetBoard(boardID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + if board == nil { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) + return + } + + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionDeleteBoard) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to delete board"}) + return + } + + auditRec := a.makeAuditRecord(r, "deleteBoard", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + auditRec.AddMeta("boardID", boardID) + + if err := a.app.DeleteBoard(boardID, userID); err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("DELETE Board", mlog.String("boardID", boardID)) + jsonStringResponse(w, http.StatusOK, "{}") + + auditRec.Success() +} + +func (a *API) handleDuplicateBoard(w http.ResponseWriter, r *http.Request) { + // swagger:operation POST /boards/{boardID}/duplicate duplicateBoard + // + // Returns the new created board and all the blocks + // + // --- + // produces: + // - application/json + // parameters: + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // $ref: '#/definitions/BoardsAndBlocks' + // '404': + // description: board not found + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + boardID := mux.Vars(r)["boardID"] + userID := getUserID(r) + query := r.URL.Query() + asTemplate := query.Get("asTemplate") + toTeam := query.Get("toTeam") + + if userID == "" { + a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "", PermissionError{"access denied to board"}) + return + } + + board, err := a.app.GetBoard(boardID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + if board == nil { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) + return + } + + if toTeam == "" && !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"}) + return + } + + if toTeam != "" && !a.permissions.HasPermissionToTeam(userID, toTeam, model.PermissionViewTeam) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"}) + return + } + + if board.IsTemplate && board.Type == model.BoardTypeOpen { + if board.TeamID != model.GlobalTeamID && !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) + return + } + } else { + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) + return + } + } + + auditRec := a.makeAuditRecord(r, "duplicateBoard", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + auditRec.AddMeta("boardID", boardID) + + a.logger.Debug("DuplicateBoard", + mlog.String("boardID", boardID), + ) + + boardsAndBlocks, _, err := a.app.DuplicateBoard(boardID, userID, toTeam, asTemplate == True) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, err.Error(), err) + return + } + + data, err := json.Marshal(boardsAndBlocks) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + // response + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.Success() +} + +func (a *API) handleUndeleteBoard(w http.ResponseWriter, r *http.Request) { + // swagger:operation POST /boards/{boardID}/undelete undeleteBoard + // + // Undeletes a board + // + // --- + // produces: + // - application/json + // parameters: + // - name: boardID + // in: path + // description: ID of board to undelete + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + ctx := r.Context() + session := ctx.Value(sessionContextKey).(*model.Session) + userID := session.UserID + + vars := mux.Vars(r) + boardID := vars["boardID"] + + auditRec := a.makeAuditRecord(r, "undeleteBoard", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + auditRec.AddMeta("boardID", boardID) + + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionDeleteBoard) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to undelete board"}) + return + } + + err := a.app.UndeleteBoard(boardID, userID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("UNDELETE Board", mlog.String("boardID", boardID)) + jsonStringResponse(w, http.StatusOK, "{}") + + auditRec.Success() +} + +func (a *API) handleGetBoardMetadata(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /boards/{boardID}/metadata getBoardMetadata + // + // Returns a board's metadata + // + // --- + // produces: + // - application/json + // parameters: + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // "$ref": "#/definitions/BoardMetadata" + // '404': + // description: board not found + // '501': + // description: required license not found + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + boardID := mux.Vars(r)["boardID"] + userID := getUserID(r) + + board, boardMetadata, err := a.app.GetBoardMetadata(boardID) + if errors.Is(err, app.ErrInsufficientLicense) { + a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "", err) + return + } + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + if board == nil || boardMetadata == nil { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) + return + } + + if board.Type == model.BoardTypePrivate { + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) + return + } + } else { + if !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) + return + } + } + + auditRec := a.makeAuditRecord(r, "getBoardMetadata", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + auditRec.AddMeta("boardID", boardID) + + data, err := json.Marshal(boardMetadata) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + // response + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.Success() +} diff --git a/server/api/boards_and_blocks.go b/server/api/boards_and_blocks.go new file mode 100644 index 000000000..9270fbf61 --- /dev/null +++ b/server/api/boards_and_blocks.go @@ -0,0 +1,426 @@ +package api + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + + "github.com/gorilla/mux" + "github.com/mattermost/focalboard/server/app" + "github.com/mattermost/focalboard/server/model" + "github.com/mattermost/focalboard/server/services/audit" + + "github.com/mattermost/mattermost-server/v6/shared/mlog" +) + +func (a *API) registerBoardsAndBlocksRoutes(r *mux.Router) { + // BoardsAndBlocks APIs + r.HandleFunc("/boards-and-blocks", a.sessionRequired(a.handleCreateBoardsAndBlocks)).Methods("POST") + r.HandleFunc("/boards-and-blocks", a.sessionRequired(a.handlePatchBoardsAndBlocks)).Methods("PATCH") + r.HandleFunc("/boards-and-blocks", a.sessionRequired(a.handleDeleteBoardsAndBlocks)).Methods("DELETE") +} + +func (a *API) handleCreateBoardsAndBlocks(w http.ResponseWriter, r *http.Request) { + // swagger:operation POST /boards-and-blocks insertBoardsAndBlocks + // + // Creates new boards and blocks + // + // --- + // produces: + // - application/json + // parameters: + // - name: Body + // in: body + // description: the boards and blocks to create + // required: true + // schema: + // "$ref": "#/definitions/BoardsAndBlocks" + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // $ref: '#/definitions/BoardsAndBlocks' + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + userID := getUserID(r) + + requestBody, err := ioutil.ReadAll(r.Body) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + var newBab *model.BoardsAndBlocks + if err = json.Unmarshal(requestBody, &newBab); err != nil { + // a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) + return + } + + if len(newBab.Boards) == 0 { + message := "at least one board is required" + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) + return + } + + teamID := "" + boardIDs := map[string]bool{} + for _, board := range newBab.Boards { + boardIDs[board.ID] = true + + if teamID == "" { + teamID = board.TeamID + continue + } + + if teamID != board.TeamID { + message := "cannot create boards for multiple teams" + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) + return + } + + if board.ID == "" { + message := "boards need an ID to be referenced from the blocks" + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) + return + } + } + + if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board template"}) + return + } + + for _, block := range newBab.Blocks { + // Error checking + if len(block.Type) < 1 { + message := fmt.Sprintf("missing type for block id %s", block.ID) + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) + return + } + + if block.CreateAt < 1 { + message := fmt.Sprintf("invalid createAt for block id %s", block.ID) + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) + return + } + + if block.UpdateAt < 1 { + message := fmt.Sprintf("invalid UpdateAt for block id %s", block.ID) + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) + return + } + + if !boardIDs[block.BoardID] { + message := fmt.Sprintf("invalid BoardID %s (not exists in the created boards)", block.BoardID) + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil) + return + } + } + + // IDs of boards and blocks are used to confirm that they're + // linked and then regenerated by the server + newBab, err = model.GenerateBoardsAndBlocksIDs(newBab, a.logger) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, err.Error(), err) + return + } + + auditRec := a.makeAuditRecord(r, "createBoardsAndBlocks", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + auditRec.AddMeta("teamID", teamID) + auditRec.AddMeta("userID", userID) + auditRec.AddMeta("boardsCount", len(newBab.Boards)) + auditRec.AddMeta("blocksCount", len(newBab.Blocks)) + + // create boards and blocks + bab, err := a.app.CreateBoardsAndBlocks(newBab, userID, true) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, err.Error(), err) + return + } + + a.logger.Debug("CreateBoardsAndBlocks", + mlog.String("teamID", teamID), + mlog.String("userID", userID), + mlog.Int("boardCount", len(bab.Boards)), + mlog.Int("blockCount", len(bab.Blocks)), + ) + + data, err := json.Marshal(bab) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, err.Error(), err) + return + } + + // response + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.Success() +} + +func (a *API) handlePatchBoardsAndBlocks(w http.ResponseWriter, r *http.Request) { + // swagger:operation PATCH /boards-and-blocks patchBoardsAndBlocks + // + // Patches a set of related boards and blocks + // + // --- + // produces: + // - application/json + // parameters: + // - name: Body + // in: body + // description: the patches for the boards and blocks + // required: true + // schema: + // "$ref": "#/definitions/PatchBoardsAndBlocks" + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // $ref: '#/definitions/BoardsAndBlocks' + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + userID := getUserID(r) + + requestBody, err := ioutil.ReadAll(r.Body) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + var pbab *model.PatchBoardsAndBlocks + if err = json.Unmarshal(requestBody, &pbab); err != nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) + return + } + + if err = pbab.IsValid(); err != nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) + return + } + + teamID := "" + boardIDMap := map[string]bool{} + for i, boardID := range pbab.BoardIDs { + boardIDMap[boardID] = true + patch := pbab.BoardPatches[i] + + if err = patch.IsValid(); err != nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) + return + } + + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardProperties) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying board properties"}) + return + } + + if patch.Type != nil || patch.MinimumRole != nil { + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardType) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying board type"}) + return + } + } + + board, err2 := a.app.GetBoard(boardID) + if err2 != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err2) + return + } + if board == nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil) + return + } + + if teamID == "" { + teamID = board.TeamID + } + if teamID != board.TeamID { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil) + return + } + } + + for _, blockID := range pbab.BlockIDs { + block, err2 := a.app.GetBlockByID(blockID) + if err2 != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err2) + return + } + if block == nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil) + return + } + + if _, ok := boardIDMap[block.BoardID]; !ok { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil) + return + } + + if !a.permissions.HasPermissionToBoard(userID, block.BoardID, model.PermissionManageBoardCards) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying cards"}) + return + } + } + + auditRec := a.makeAuditRecord(r, "patchBoardsAndBlocks", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + auditRec.AddMeta("boardsCount", len(pbab.BoardIDs)) + auditRec.AddMeta("blocksCount", len(pbab.BlockIDs)) + + bab, err := a.app.PatchBoardsAndBlocks(pbab, userID) + if errors.Is(err, app.ErrPatchUpdatesLimitedCards) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", err) + return + } + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("PATCH BoardsAndBlocks", + mlog.Int("boardsCount", len(pbab.BoardIDs)), + mlog.Int("blocksCount", len(pbab.BlockIDs)), + ) + + data, err := json.Marshal(bab) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + // response + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.Success() +} + +func (a *API) handleDeleteBoardsAndBlocks(w http.ResponseWriter, r *http.Request) { + // swagger:operation DELETE /boards-and-blocks deleteBoardsAndBlocks + // + // Deletes boards and blocks + // + // --- + // produces: + // - application/json + // parameters: + // - name: Body + // in: body + // description: the boards and blocks to delete + // required: true + // schema: + // "$ref": "#/definitions/DeleteBoardsAndBlocks" + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + userID := getUserID(r) + + requestBody, err := ioutil.ReadAll(r.Body) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + var dbab *model.DeleteBoardsAndBlocks + if err = json.Unmarshal(requestBody, &dbab); err != nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) + return + } + + // user must have permission to delete all the boards, and that + // would include the permission to manage their blocks + teamID := "" + boardIDMap := map[string]bool{} + for _, boardID := range dbab.Boards { + boardIDMap[boardID] = true + // all boards in the request should belong to the same team + board, err := a.app.GetBoard(boardID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + if board == nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) + return + } + if teamID == "" { + teamID = board.TeamID + } + if teamID != board.TeamID { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil) + return + } + + // permission check + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionDeleteBoard) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to delete board"}) + return + } + } + + for _, blockID := range dbab.Blocks { + block, err2 := a.app.GetBlockByID(blockID) + if err2 != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err2) + return + } + if block == nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil) + return + } + + if _, ok := boardIDMap[block.BoardID]; !ok { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil) + return + } + + if !a.permissions.HasPermissionToBoard(userID, block.BoardID, model.PermissionManageBoardCards) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modifying cards"}) + return + } + } + + if err := dbab.IsValid(); err != nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) + return + } + + auditRec := a.makeAuditRecord(r, "deleteBoardsAndBlocks", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + auditRec.AddMeta("boardsCount", len(dbab.Boards)) + auditRec.AddMeta("blocksCount", len(dbab.Blocks)) + + if err := a.app.DeleteBoardsAndBlocks(dbab, userID); err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("DELETE BoardsAndBlocks", + mlog.Int("boardsCount", len(dbab.Boards)), + mlog.Int("blocksCount", len(dbab.Blocks)), + ) + + // response + jsonStringResponse(w, http.StatusOK, "{}") + auditRec.Success() +} diff --git a/server/api/categories.go b/server/api/categories.go new file mode 100644 index 000000000..e26e1d1bd --- /dev/null +++ b/server/api/categories.go @@ -0,0 +1,401 @@ +package api + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + + "github.com/gorilla/mux" + "github.com/mattermost/focalboard/server/app" + "github.com/mattermost/focalboard/server/model" + "github.com/mattermost/focalboard/server/services/audit" +) + +func (a *API) registerCategoriesRoutes(r *mux.Router) { + // Category APIs + r.HandleFunc("/teams/{teamID}/categories", a.sessionRequired(a.handleCreateCategory)).Methods(http.MethodPost) + r.HandleFunc("/teams/{teamID}/categories/{categoryID}", a.sessionRequired(a.handleUpdateCategory)).Methods(http.MethodPut) + r.HandleFunc("/teams/{teamID}/categories/{categoryID}", a.sessionRequired(a.handleDeleteCategory)).Methods(http.MethodDelete) + r.HandleFunc("/teams/{teamID}/categories", a.sessionRequired(a.handleGetUserCategoryBoards)).Methods(http.MethodGet) + r.HandleFunc("/teams/{teamID}/categories/{categoryID}/boards/{boardID}", a.sessionRequired(a.handleUpdateCategoryBoard)).Methods(http.MethodPost) +} + +func (a *API) handleCreateCategory(w http.ResponseWriter, r *http.Request) { + // swagger:operation POST /teams/{teamID}/categories createCategory + // + // Create a category for boards + // + // --- + // produces: + // - application/json + // parameters: + // - name: teamID + // in: path + // description: Team ID + // required: true + // type: string + // - name: Body + // in: body + // description: category to create + // required: true + // schema: + // "$ref": "#/definitions/Category" + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // "$ref": "#/definitions/Category" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + requestBody, err := ioutil.ReadAll(r.Body) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + var category model.Category + + err = json.Unmarshal(requestBody, &category) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + auditRec := a.makeAuditRecord(r, "createCategory", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + + ctx := r.Context() + session := ctx.Value(sessionContextKey).(*model.Session) + + // user can only create category for themselves + if category.UserID != session.UserID { + a.errorResponse( + w, + r.URL.Path, + http.StatusBadRequest, + fmt.Sprintf("userID %s and category userID %s mismatch", session.UserID, category.UserID), + nil, + ) + return + } + + vars := mux.Vars(r) + teamID := vars["teamID"] + + if category.TeamID != teamID { + a.errorResponse( + w, + r.URL.Path, + http.StatusBadRequest, + "teamID mismatch", + nil, + ) + return + } + + createdCategory, err := a.app.CreateCategory(&category) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + data, err := json.Marshal(createdCategory) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + jsonBytesResponse(w, http.StatusOK, data) + auditRec.AddMeta("categoryID", createdCategory.ID) + auditRec.Success() +} + +func (a *API) handleUpdateCategory(w http.ResponseWriter, r *http.Request) { + // swagger:operation PUT /teams/{teamID}/categories/{categoryID} updateCategory + // + // Create a category for boards + // + // --- + // produces: + // - application/json + // parameters: + // - name: teamID + // in: path + // description: Team ID + // required: true + // type: string + // - name: categoryID + // in: path + // description: Category ID + // required: true + // type: string + // - name: Body + // in: body + // description: category to update + // required: true + // schema: + // "$ref": "#/definitions/Category" + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // "$ref": "#/definitions/Category" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + vars := mux.Vars(r) + categoryID := vars["categoryID"] + + requestBody, err := ioutil.ReadAll(r.Body) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + var category model.Category + err = json.Unmarshal(requestBody, &category) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + auditRec := a.makeAuditRecord(r, "updateCategory", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + + if categoryID != category.ID { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "categoryID mismatch in patch and body", nil) + return + } + + ctx := r.Context() + session := ctx.Value(sessionContextKey).(*model.Session) + + // user can only update category for themselves + if category.UserID != session.UserID { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "user ID mismatch in session and category", nil) + return + } + + teamID := vars["teamID"] + if category.TeamID != teamID { + a.errorResponse( + w, + r.URL.Path, + http.StatusBadRequest, + "teamID mismatch", + nil, + ) + return + } + + updatedCategory, err := a.app.UpdateCategory(&category) + if err != nil { + switch { + case errors.Is(err, app.ErrorCategoryDeleted): + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", err) + case errors.Is(err, app.ErrorCategoryPermissionDenied): + // TODO: The permissions should be handled as much as possible at + // the API level, this needs to be changed + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", err) + default: + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + } + return + } + + data, err := json.Marshal(updatedCategory) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + jsonBytesResponse(w, http.StatusOK, data) + auditRec.Success() +} + +func (a *API) handleDeleteCategory(w http.ResponseWriter, r *http.Request) { + // swagger:operation DELETE /teams/{teamID}/categories/{categoryID} deleteCategory + // + // Delete a category + // + // --- + // produces: + // - application/json + // parameters: + // - name: teamID + // in: path + // description: Team ID + // required: true + // type: string + // - name: categoryID + // in: path + // description: Category ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + ctx := r.Context() + session := ctx.Value(sessionContextKey).(*model.Session) + vars := mux.Vars(r) + + userID := session.UserID + teamID := vars["teamID"] + categoryID := vars["categoryID"] + + auditRec := a.makeAuditRecord(r, "deleteCategory", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + + deletedCategory, err := a.app.DeleteCategory(categoryID, userID, teamID) + if err != nil { + switch { + case errors.Is(err, app.ErrorInvalidCategory): + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) + case errors.Is(err, app.ErrorCategoryPermissionDenied): + // TODO: The permissions should be handled as much as possible at + // the API level, this needs to be changed + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", err) + default: + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + } + return + } + + data, err := json.Marshal(deletedCategory) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + jsonBytesResponse(w, http.StatusOK, data) + auditRec.Success() +} + +func (a *API) handleGetUserCategoryBoards(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /teams/{teamID}/categories getUserCategoryBoards + // + // Gets the user's board categories + // + // --- + // produces: + // - application/json + // parameters: + // - name: teamID + // in: path + // description: Team ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // items: + // "$ref": "#/definitions/CategoryBoards" + // type: array + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + ctx := r.Context() + session := ctx.Value(sessionContextKey).(*model.Session) + userID := session.UserID + + vars := mux.Vars(r) + teamID := vars["teamID"] + + auditRec := a.makeAuditRecord(r, "getUserCategoryBoards", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + + categoryBlocks, err := a.app.GetUserCategoryBoards(userID, teamID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + data, err := json.Marshal(categoryBlocks) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + jsonBytesResponse(w, http.StatusOK, data) + auditRec.Success() +} + +func (a *API) handleUpdateCategoryBoard(w http.ResponseWriter, r *http.Request) { + // swagger:operation POST /teams/{teamID}/categories/{categoryID}/boards/{boardID} updateCategoryBoard + // + // Set the category of a board + // + // --- + // produces: + // - application/json + // parameters: + // - name: teamID + // in: path + // description: Team ID + // required: true + // type: string + // - name: categoryID + // in: path + // description: Category ID + // required: true + // type: string + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + auditRec := a.makeAuditRecord(r, "updateCategoryBoard", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + + vars := mux.Vars(r) + categoryID := vars["categoryID"] + boardID := vars["boardID"] + teamID := vars["teamID"] + + ctx := r.Context() + session := ctx.Value(sessionContextKey).(*model.Session) + userID := session.UserID + + // TODO: Check the category and the team matches + err := a.app.AddUpdateUserCategoryBoard(teamID, userID, categoryID, boardID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + jsonBytesResponse(w, http.StatusOK, []byte("ok")) + auditRec.Success() +} diff --git a/server/api/channels.go b/server/api/channels.go new file mode 100644 index 000000000..16f5f2d49 --- /dev/null +++ b/server/api/channels.go @@ -0,0 +1,104 @@ +package api + +import ( + "encoding/json" + "net/http" + + "github.com/gorilla/mux" + "github.com/mattermost/focalboard/server/model" + "github.com/mattermost/focalboard/server/services/audit" + + mm_model "github.com/mattermost/mattermost-server/v6/model" + "github.com/mattermost/mattermost-server/v6/shared/mlog" +) + +func (a *API) registerChannelsRoutes(r *mux.Router) { + r.HandleFunc("/teams/{teamID}/channels/{channelID}", a.sessionRequired(a.handleGetChannel)).Methods("GET") +} + +func (a *API) handleGetChannel(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /teams/{teamID}/channels/{channelID} getChannel + // + // Returns the requested channel + // + // --- + // produces: + // - application/json + // parameters: + // - name: teamID + // in: path + // description: Team ID + // required: true + // type: string + // - name: channelID + // in: path + // description: Channel ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // type: array + // items: + // "$ref": "#/definitions/Channel" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + if !a.MattermostAuth { + a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in standalone mode", nil) + return + } + + teamID := mux.Vars(r)["teamID"] + channelID := mux.Vars(r)["channelID"] + userID := getUserID(r) + + if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"}) + return + } + + if !a.permissions.HasPermissionToChannel(userID, channelID, model.PermissionReadChannel) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to channel"}) + return + } + + auditRec := a.makeAuditRecord(r, "getChannel", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + auditRec.AddMeta("teamID", teamID) + auditRec.AddMeta("channelID", teamID) + + channel, err := a.app.GetChannel(teamID, channelID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("GetChannel", + mlog.String("teamID", teamID), + mlog.String("channelID", channelID), + ) + + if channel.TeamId != teamID { + if channel.Type != mm_model.ChannelTypeDirect && channel.Type != mm_model.ChannelTypeGroup { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) + return + } + } + + data, err := json.Marshal(channel) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + // response + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.Success() +} diff --git a/server/api/config.go b/server/api/config.go new file mode 100644 index 000000000..db2b017cd --- /dev/null +++ b/server/api/config.go @@ -0,0 +1,41 @@ +package api + +import ( + "encoding/json" + "net/http" + + "github.com/gorilla/mux" +) + +func (a *API) registerConfigRoutes(r *mux.Router) { + // Config APIs + r.HandleFunc("/clientConfig", a.getClientConfig).Methods("GET") +} + +func (a *API) getClientConfig(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /clientConfig getClientConfig + // + // Returns the client configuration + // + // --- + // produces: + // - application/json + // responses: + // '200': + // description: success + // schema: + // "$ref": "#/definitions/ClientConfig" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + clientConfig := a.app.GetClientConfig() + + configData, err := json.Marshal(clientConfig) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + jsonBytesResponse(w, http.StatusOK, configData) +} diff --git a/server/api/files.go b/server/api/files.go new file mode 100644 index 000000000..8c3199f83 --- /dev/null +++ b/server/api/files.go @@ -0,0 +1,259 @@ +package api + +import ( + "encoding/json" + "io" + "net/http" + "path/filepath" + "strings" + "time" + + "github.com/gorilla/mux" + "github.com/mattermost/focalboard/server/model" + "github.com/mattermost/focalboard/server/services/audit" + + "github.com/mattermost/mattermost-server/v6/shared/mlog" +) + +// FileUploadResponse is the response to a file upload +// swagger:model +type FileUploadResponse struct { + // The FileID to retrieve the uploaded file + // required: true + FileID string `json:"fileId"` +} + +func FileUploadResponseFromJSON(data io.Reader) (*FileUploadResponse, error) { + var fileUploadResponse FileUploadResponse + + if err := json.NewDecoder(data).Decode(&fileUploadResponse); err != nil { + return nil, err + } + return &fileUploadResponse, nil +} + +func (a *API) registerFilesRoutes(r *mux.Router) { + // Files API + r.HandleFunc("/files/teams/{teamID}/{boardID}/{filename}", a.attachSession(a.handleServeFile, false)).Methods("GET") + r.HandleFunc("/teams/{teamID}/{boardID}/files", a.sessionRequired(a.handleUploadFile)).Methods("POST") +} + +func (a *API) handleServeFile(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /files/teams/{teamID}/{boardID}/{filename} getFile + // + // Returns the contents of an uploaded file + // + // --- + // produces: + // - application/json + // - image/jpg + // - image/png + // - image/gif + // parameters: + // - name: teamID + // in: path + // description: Team ID + // required: true + // type: string + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // - name: filename + // in: path + // description: name of the file + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // '404': + // description: file not found + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + vars := mux.Vars(r) + boardID := vars["boardID"] + filename := vars["filename"] + userID := getUserID(r) + + hasValidReadToken := a.hasValidReadTokenForBoard(r, boardID) + if userID == "" && !hasValidReadToken { + a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "", nil) + return + } + + if !hasValidReadToken && !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) + return + } + + board, err := a.app.GetBoard(boardID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + if board == nil { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) + return + } + + auditRec := a.makeAuditRecord(r, "getFile", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + auditRec.AddMeta("boardID", boardID) + auditRec.AddMeta("teamID", board.TeamID) + auditRec.AddMeta("filename", filename) + + contentType := "image/jpg" + + fileExtension := strings.ToLower(filepath.Ext(filename)) + if fileExtension == "png" { + contentType = "image/png" + } + + if fileExtension == "gif" { + contentType = "image/gif" + } + + w.Header().Set("Content-Type", contentType) + + fileInfo, err := a.app.GetFileInfo(filename) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + if fileInfo != nil && fileInfo.Archived { + fileMetadata := map[string]interface{}{ + "archived": true, + "name": fileInfo.Name, + "size": fileInfo.Size, + "extension": fileInfo.Extension, + } + + data, jsonErr := json.Marshal(fileMetadata) + if jsonErr != nil { + a.logger.Error("failed to marshal archived file metadata", mlog.String("filename", filename), mlog.Err(jsonErr)) + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", jsonErr) + return + } + + jsonBytesResponse(w, http.StatusBadRequest, data) + return + } + + fileReader, err := a.app.GetFileReader(board.TeamID, boardID, filename) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + defer fileReader.Close() + http.ServeContent(w, r, filename, time.Now(), fileReader) + auditRec.Success() +} + +func (a *API) handleUploadFile(w http.ResponseWriter, r *http.Request) { + // swagger:operation POST /teams/{teamID}/boards/{boardID}/files uploadFile + // + // Upload a binary file, attached to a root block + // + // --- + // consumes: + // - multipart/form-data + // produces: + // - application/json + // parameters: + // - name: teamID + // in: path + // description: ID of the team + // required: true + // type: string + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // - name: uploaded file + // in: formData + // type: file + // description: The file to upload + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // "$ref": "#/definitions/FileUploadResponse" + // '404': + // description: board not found + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + vars := mux.Vars(r) + boardID := vars["boardID"] + userID := getUserID(r) + + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"}) + return + } + + board, err := a.app.GetBoard(boardID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + if board == nil { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) + return + } + + if a.app.GetConfig().MaxFileSize > 0 { + r.Body = http.MaxBytesReader(w, r.Body, a.app.GetConfig().MaxFileSize) + } + + file, handle, err := r.FormFile(UploadFormFileKey) + if err != nil { + if strings.HasSuffix(err.Error(), "http: request body too large") { + a.errorResponse(w, r.URL.Path, http.StatusRequestEntityTooLarge, "", err) + return + } + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) + return + } + defer file.Close() + + auditRec := a.makeAuditRecord(r, "uploadFile", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + auditRec.AddMeta("boardID", boardID) + auditRec.AddMeta("teamID", board.TeamID) + auditRec.AddMeta("filename", handle.Filename) + + fileID, err := a.app.SaveFile(file, board.TeamID, boardID, handle.Filename) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("uploadFile", + mlog.String("filename", handle.Filename), + mlog.String("fileID", fileID), + ) + data, err := json.Marshal(FileUploadResponse{FileID: fileID}) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.AddMeta("fileID", fileID) + auditRec.Success() +} diff --git a/server/api/insights.go b/server/api/insights.go new file mode 100644 index 000000000..efada46cd --- /dev/null +++ b/server/api/insights.go @@ -0,0 +1,242 @@ +package api + +import ( + "encoding/json" + "net/http" + "strconv" + "time" + + "github.com/gorilla/mux" + "github.com/mattermost/focalboard/server/model" + "github.com/mattermost/focalboard/server/services/audit" + + mmModel "github.com/mattermost/mattermost-server/v6/model" +) + +func (a *API) registerInsightsRoutes(r *mux.Router) { + // Insights APIs + r.HandleFunc("/teams/{teamID}/boards/insights", a.sessionRequired(a.handleTeamBoardsInsights)).Methods("GET") + r.HandleFunc("/users/me/boards/insights", a.sessionRequired(a.handleUserBoardsInsights)).Methods("GET") +} + +func (a *API) handleTeamBoardsInsights(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /teams/{teamID}/boards/insights handleTeamBoardsInsights + // + // Returns team boards insights + // + // --- + // produces: + // - application/json + // parameters: + // - name: teamID + // in: path + // description: Team ID + // required: true + // type: string + // - name: time_range + // in: query + // description: duration of data to calculate insights for + // required: true + // type: string + // - name: page + // in: query + // description: page offset for top boards + // required: true + // type: string + // - name: per_page + // in: query + // description: limit for boards in a page. + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // type: array + // items: + // "$ref": "#/definitions/BoardInsight" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + if !a.MattermostAuth { + a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in standalone mode", nil) + return + } + + vars := mux.Vars(r) + teamID := vars["teamID"] + userID := getUserID(r) + query := r.URL.Query() + timeRange := query.Get("time_range") + + if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "Access denied to team", PermissionError{"access denied to team"}) + return + } + + auditRec := a.makeAuditRecord(r, "getTeamBoardsInsights", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + + page, err := strconv.Atoi(query.Get("page")) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "error converting page parameter to integer", err) + return + } + if page < 0 { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "Invalid page parameter", nil) + } + + perPage, err := strconv.Atoi(query.Get("per_page")) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "error converting per_page parameter to integer", err) + return + } + if perPage < 0 { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "Invalid page parameter", nil) + } + + userTimezone, aErr := a.app.GetUserTimezone(userID) + if aErr != nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "Error getting time zone of user", aErr) + return + } + userLocation, _ := time.LoadLocation(userTimezone) + if userLocation == nil { + userLocation = time.Now().UTC().Location() + } + // get unix time for duration + startTime := mmModel.StartOfDayForTimeRange(timeRange, userLocation) + boardsInsights, err := a.app.GetTeamBoardsInsights(userID, teamID, &mmModel.InsightsOpts{ + StartUnixMilli: mmModel.GetMillisForTime(*startTime), + Page: page, + PerPage: perPage, + }) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "time_range="+timeRange, err) + return + } + + data, err := json.Marshal(boardsInsights) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.AddMeta("teamBoardsInsightCount", len(boardsInsights.Items)) + auditRec.Success() +} + +func (a *API) handleUserBoardsInsights(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /users/me/boards/insights getUserBoardsInsights + // + // Returns user boards insights + // + // --- + // produces: + // - application/json + // parameters: + // - name: teamID + // in: path + // description: Team ID + // required: true + // type: string + // - name: time_range + // in: query + // description: duration of data to calculate insights for + // required: true + // type: string + // - name: page + // in: query + // description: page offset for top boards + // required: true + // type: string + // - name: per_page + // in: query + // description: limit for boards in a page. + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // type: array + // items: + // "$ref": "#/definitions/BoardInsight" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + if !a.MattermostAuth { + a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in standalone mode", nil) + return + } + + userID := getUserID(r) + query := r.URL.Query() + teamID := query.Get("team_id") + timeRange := query.Get("time_range") + + if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "Access denied to team", PermissionError{"access denied to team"}) + return + } + + auditRec := a.makeAuditRecord(r, "getUserBoardsInsights", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + page, err := strconv.Atoi(query.Get("page")) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "error converting page parameter to integer", err) + return + } + + if page < 0 { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "Invalid page parameter", nil) + } + perPage, err := strconv.Atoi(query.Get("per_page")) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "error converting per_page parameter to integer", err) + return + } + + if perPage < 0 { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "Invalid page parameter", nil) + } + userTimezone, aErr := a.app.GetUserTimezone(userID) + if aErr != nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "Error getting time zone of user", aErr) + return + } + userLocation, _ := time.LoadLocation(userTimezone) + if userLocation == nil { + userLocation = time.Now().UTC().Location() + } + // get unix time for duration + startTime := mmModel.StartOfDayForTimeRange(timeRange, userLocation) + boardsInsights, err := a.app.GetUserBoardsInsights(userID, teamID, &mmModel.InsightsOpts{ + StartUnixMilli: mmModel.GetMillisForTime(*startTime), + Page: page, + PerPage: perPage, + }) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "time_range="+timeRange, err) + return + } + data, err := json.Marshal(boardsInsights) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.AddMeta("userBoardInsightCount", len(boardsInsights.Items)) + auditRec.Success() +} diff --git a/server/api/limits.go b/server/api/limits.go new file mode 100644 index 000000000..cf6b73f63 --- /dev/null +++ b/server/api/limits.go @@ -0,0 +1,80 @@ +package api + +import ( + "encoding/json" + "net/http" + + "github.com/gorilla/mux" +) + +func (a *API) registerLimitsRoutes(r *mux.Router) { + // limits + r.HandleFunc("/limits", a.sessionRequired(a.handleCloudLimits)).Methods("GET") + r.HandleFunc("/teams/{teamID}/notifyadminupgrade", a.sessionRequired(a.handleNotifyAdminUpgrade)).Methods(http.MethodPost) +} + +func (a *API) handleCloudLimits(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /limits cloudLimits + // + // Fetches the cloud limits of the server. + // + // --- + // produces: + // - application/json + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // "$ref": "#/definitions/BoardsCloudLimits" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + boardsCloudLimits, err := a.app.GetBoardsCloudLimits() + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + data, err := json.Marshal(boardsCloudLimits) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + jsonBytesResponse(w, http.StatusOK, data) +} + +func (a *API) handleNotifyAdminUpgrade(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /api/v2/teams/{teamID}/notifyadminupgrade handleNotifyAdminUpgrade + // + // Notifies admins for upgrade request. + // + // --- + // produces: + // - application/json + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + if !a.MattermostAuth { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", errAPINotSupportedInStandaloneMode) + return + } + + vars := mux.Vars(r) + teamID := vars["teamID"] + + if err := a.app.NotifyPortalAdminsUpgradeRequest(teamID); err != nil { + jsonStringResponse(w, http.StatusOK, "{}") + } +} diff --git a/server/api/members.go b/server/api/members.go new file mode 100644 index 000000000..f377a2233 --- /dev/null +++ b/server/api/members.go @@ -0,0 +1,535 @@ +package api + +import ( + "encoding/json" + "errors" + "io/ioutil" + "net/http" + + "github.com/gorilla/mux" + "github.com/mattermost/focalboard/server/app" + "github.com/mattermost/focalboard/server/model" + "github.com/mattermost/focalboard/server/services/audit" + "github.com/mattermost/mattermost-server/v6/shared/mlog" +) + +func (a *API) registerMembersRoutes(r *mux.Router) { + // Member APIs + r.HandleFunc("/boards/{boardID}/members", a.sessionRequired(a.handleGetMembersForBoard)).Methods("GET") + r.HandleFunc("/boards/{boardID}/members", a.sessionRequired(a.handleAddMember)).Methods("POST") + r.HandleFunc("/boards/{boardID}/members/{userID}", a.sessionRequired(a.handleUpdateMember)).Methods("PUT") + r.HandleFunc("/boards/{boardID}/members/{userID}", a.sessionRequired(a.handleDeleteMember)).Methods("DELETE") + r.HandleFunc("/boards/{boardID}/join", a.sessionRequired(a.handleJoinBoard)).Methods("POST") + r.HandleFunc("/boards/{boardID}/leave", a.sessionRequired(a.handleLeaveBoard)).Methods("POST") +} + +func (a *API) handleGetMembersForBoard(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /boards/{boardID}/members getMembersForBoard + // + // Returns the members of the board + // + // --- + // produces: + // - application/json + // parameters: + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // type: array + // items: + // "$ref": "#/definitions/BoardMember" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + boardID := mux.Vars(r)["boardID"] + userID := getUserID(r) + + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board members"}) + return + } + + auditRec := a.makeAuditRecord(r, "getMembersForBoard", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + auditRec.AddMeta("boardID", boardID) + + members, err := a.app.GetMembersForBoard(boardID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("GetMembersForBoard", + mlog.String("boardID", boardID), + mlog.Int("membersCount", len(members)), + ) + + data, err := json.Marshal(members) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + // response + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.Success() +} + +func (a *API) handleAddMember(w http.ResponseWriter, r *http.Request) { + // swagger:operation POST /boards/{boardID}/members addMember + // + // Adds a new member to a board + // + // --- + // produces: + // - application/json + // parameters: + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // - name: Body + // in: body + // description: membership to replace the current one with + // required: true + // schema: + // "$ref": "#/definitions/BoardMember" + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // $ref: '#/definitions/BoardMember' + // '404': + // description: board not found + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + boardID := mux.Vars(r)["boardID"] + userID := getUserID(r) + + board, err := a.app.GetBoard(boardID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + if board == nil { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) + return + } + + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardRoles) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modify board members"}) + return + } + + requestBody, err := ioutil.ReadAll(r.Body) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + var reqBoardMember *model.BoardMember + if err = json.Unmarshal(requestBody, &reqBoardMember); err != nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) + return + } + + if reqBoardMember.UserID == "" { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) + return + } + + // currently all memberships are created as editors by default + newBoardMember := &model.BoardMember{ + UserID: reqBoardMember.UserID, + BoardID: boardID, + SchemeEditor: true, + } + + auditRec := a.makeAuditRecord(r, "addMember", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + auditRec.AddMeta("boardID", boardID) + auditRec.AddMeta("addedUserID", reqBoardMember.UserID) + + member, err := a.app.AddMemberToBoard(newBoardMember) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("AddMember", + mlog.String("boardID", board.ID), + mlog.String("addedUserID", reqBoardMember.UserID), + ) + + data, err := json.Marshal(member) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + // response + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.Success() +} + +func (a *API) handleJoinBoard(w http.ResponseWriter, r *http.Request) { + // swagger:operation POST /boards/{boardID}/join joinBoard + // + // Become a member of a board + // + // --- + // produces: + // - application/json + // parameters: + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // $ref: '#/definitions/BoardMember' + // '404': + // description: board not found + // '403': + // description: access denied + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + userID := getUserID(r) + if userID == "" { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil) + return + } + + boardID := mux.Vars(r)["boardID"] + board, err := a.app.GetBoard(boardID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + if board == nil { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) + return + } + if board.Type != model.BoardTypeOpen { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", nil) + return + } + + if !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", nil) + return + } + + newBoardMember := &model.BoardMember{ + UserID: userID, + BoardID: boardID, + SchemeAdmin: board.MinimumRole == model.BoardRoleAdmin, + SchemeEditor: board.MinimumRole == model.BoardRoleNone || board.MinimumRole == model.BoardRoleEditor, + SchemeCommenter: board.MinimumRole == model.BoardRoleCommenter, + SchemeViewer: board.MinimumRole == model.BoardRoleViewer, + } + + auditRec := a.makeAuditRecord(r, "joinBoard", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + auditRec.AddMeta("boardID", boardID) + auditRec.AddMeta("addedUserID", userID) + + member, err := a.app.AddMemberToBoard(newBoardMember) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("JoinBoard", + mlog.String("boardID", board.ID), + mlog.String("addedUserID", userID), + ) + + data, err := json.Marshal(member) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + // response + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.Success() +} + +func (a *API) handleLeaveBoard(w http.ResponseWriter, r *http.Request) { + // swagger:operation POST /boards/{boardID}/leave leaveBoard + // + // Remove your own membership from a board + // + // --- + // produces: + // - application/json + // parameters: + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // '404': + // description: board not found + // '403': + // description: access denied + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + userID := getUserID(r) + if userID == "" { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", nil) + return + } + + boardID := mux.Vars(r)["boardID"] + + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", nil) + return + } + + board, err := a.app.GetBoard(boardID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + if board == nil { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", nil) + return + } + + auditRec := a.makeAuditRecord(r, "leaveBoard", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + auditRec.AddMeta("boardID", boardID) + auditRec.AddMeta("addedUserID", userID) + + err = a.app.DeleteBoardMember(boardID, userID) + if errors.Is(err, app.ErrBoardMemberIsLastAdmin) { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) + return + } + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("LeaveBoard", + mlog.String("boardID", board.ID), + mlog.String("addedUserID", userID), + ) + + jsonStringResponse(w, http.StatusOK, "{}") + + auditRec.Success() +} + +func (a *API) handleUpdateMember(w http.ResponseWriter, r *http.Request) { + // swagger:operation PUT /boards/{boardID}/members/{userID} updateMember + // + // Updates a board member + // + // --- + // produces: + // - application/json + // parameters: + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // - name: userID + // in: path + // description: User ID + // required: true + // type: string + // - name: Body + // in: body + // description: membership to replace the current one with + // required: true + // schema: + // "$ref": "#/definitions/BoardMember" + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // $ref: '#/definitions/BoardMember' + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + boardID := mux.Vars(r)["boardID"] + paramsUserID := mux.Vars(r)["userID"] + userID := getUserID(r) + + requestBody, err := ioutil.ReadAll(r.Body) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + var reqBoardMember *model.BoardMember + if err = json.Unmarshal(requestBody, &reqBoardMember); err != nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) + return + } + + newBoardMember := &model.BoardMember{ + UserID: paramsUserID, + BoardID: boardID, + SchemeAdmin: reqBoardMember.SchemeAdmin, + SchemeEditor: reqBoardMember.SchemeEditor, + SchemeCommenter: reqBoardMember.SchemeCommenter, + SchemeViewer: reqBoardMember.SchemeViewer, + } + + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardRoles) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modify board members"}) + return + } + + auditRec := a.makeAuditRecord(r, "patchMember", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + auditRec.AddMeta("boardID", boardID) + auditRec.AddMeta("patchedUserID", paramsUserID) + + member, err := a.app.UpdateBoardMember(newBoardMember) + if errors.Is(err, app.ErrBoardMemberIsLastAdmin) { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) + return + } + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("PatchMember", + mlog.String("boardID", boardID), + mlog.String("patchedUserID", paramsUserID), + ) + + data, err := json.Marshal(member) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + // response + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.Success() +} + +func (a *API) handleDeleteMember(w http.ResponseWriter, r *http.Request) { + // swagger:operation DELETE /boards/{boardID}/members/{userID} deleteMember + // + // Deletes a member from a board + // + // --- + // produces: + // - application/json + // parameters: + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // - name: userID + // in: path + // description: User ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // '404': + // description: board not found + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + boardID := mux.Vars(r)["boardID"] + paramsUserID := mux.Vars(r)["userID"] + userID := getUserID(r) + + board, err := a.app.GetBoard(boardID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + if board == nil { + a.errorResponse(w, r.URL.Path, http.StatusNotFound, "", err) + return + } + + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardRoles) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modify board members"}) + return + } + + auditRec := a.makeAuditRecord(r, "deleteMember", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + auditRec.AddMeta("boardID", boardID) + auditRec.AddMeta("addedUserID", paramsUserID) + + deleteErr := a.app.DeleteBoardMember(boardID, paramsUserID) + if errors.Is(deleteErr, app.ErrBoardMemberIsLastAdmin) { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", deleteErr) + return + } + if deleteErr != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", deleteErr) + return + } + + a.logger.Debug("DeleteMember", + mlog.String("boardID", boardID), + mlog.String("addedUserID", paramsUserID), + ) + + // response + jsonStringResponse(w, http.StatusOK, "{}") + + auditRec.Success() +} diff --git a/server/api/onboarding.go b/server/api/onboarding.go new file mode 100644 index 000000000..e8b1a4c76 --- /dev/null +++ b/server/api/onboarding.go @@ -0,0 +1,73 @@ +package api + +import ( + "encoding/json" + "net/http" + + "github.com/gorilla/mux" + "github.com/mattermost/focalboard/server/model" +) + +func (a *API) registerOnboardingRoutes(r *mux.Router) { + // Onboarding tour endpoints APIs + r.HandleFunc("/teams/{teamID}/onboard", a.sessionRequired(a.handleOnboard)).Methods(http.MethodPost) +} + +func (a *API) handleOnboard(w http.ResponseWriter, r *http.Request) { + // swagger:operation POST /team/{teamID}/onboard onboard + // + // Onboards a user on Boards. + // + // --- + // produces: + // - application/json + // parameters: + // - name: teamID + // in: path + // description: Team ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // type: object + // properties: + // teamID: + // type: string + // description: Team ID + // boardID: + // type: string + // description: Board ID + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + teamID := mux.Vars(r)["teamID"] + userID := getUserID(r) + + if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to create board"}) + return + } + + teamID, boardID, err := a.app.PrepareOnboardingTour(userID, teamID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + response := map[string]string{ + "teamID": teamID, + "boardID": boardID, + } + data, err := json.Marshal(response) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + jsonBytesResponse(w, http.StatusOK, data) +} diff --git a/server/api/search.go b/server/api/search.go new file mode 100644 index 000000000..1507d6d10 --- /dev/null +++ b/server/api/search.go @@ -0,0 +1,324 @@ +package api + +import ( + "encoding/json" + "net/http" + + "github.com/gorilla/mux" + "github.com/mattermost/focalboard/server/model" + "github.com/mattermost/focalboard/server/services/audit" + + "github.com/mattermost/mattermost-server/v6/shared/mlog" +) + +func (a *API) registerSearchRoutes(r *mux.Router) { + r.HandleFunc("/teams/{teamID}/channels", a.sessionRequired(a.handleSearchMyChannels)).Methods("GET") + r.HandleFunc("/teams/{teamID}/boards/search", a.sessionRequired(a.handleSearchBoards)).Methods("GET") + r.HandleFunc("/teams/{teamID}/boards/search/linkable", a.sessionRequired(a.handleSearchLinkableBoards)).Methods("GET") + r.HandleFunc("/boards/search", a.sessionRequired(a.handleSearchAllBoards)).Methods("GET") +} + +func (a *API) handleSearchMyChannels(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /teams/{teamID}/channels searchMyChannels + // + // Returns the user available channels + // + // --- + // produces: + // - application/json + // parameters: + // - name: teamID + // in: path + // description: Team ID + // required: true + // type: string + // - name: search + // in: query + // description: string to filter channels list + // required: false + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // type: array + // items: + // "$ref": "#/definitions/Channel" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + if !a.MattermostAuth { + a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in standalone mode", nil) + return + } + + query := r.URL.Query() + searchQuery := query.Get("search") + + teamID := mux.Vars(r)["teamID"] + userID := getUserID(r) + + if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"}) + return + } + + auditRec := a.makeAuditRecord(r, "searchMyChannels", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + auditRec.AddMeta("teamID", teamID) + + channels, err := a.app.SearchUserChannels(teamID, userID, searchQuery) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("GetUserChannels", + mlog.String("teamID", teamID), + mlog.Int("channelsCount", len(channels)), + ) + + data, err := json.Marshal(channels) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + // response + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.AddMeta("channelsCount", len(channels)) + auditRec.Success() +} + +func (a *API) handleSearchBoards(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /teams/{teamID}/boards/search searchBoards + // + // Returns the boards that match with a search term in the team + // + // --- + // produces: + // - application/json + // parameters: + // - name: teamID + // in: path + // description: Team ID + // required: true + // type: string + // - name: q + // in: query + // description: The search term. Must have at least one character + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // type: array + // items: + // "$ref": "#/definitions/Board" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + teamID := mux.Vars(r)["teamID"] + term := r.URL.Query().Get("q") + userID := getUserID(r) + + if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"}) + return + } + + if len(term) == 0 { + jsonStringResponse(w, http.StatusOK, "[]") + return + } + + auditRec := a.makeAuditRecord(r, "searchBoards", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + auditRec.AddMeta("teamID", teamID) + + // retrieve boards list + boards, err := a.app.SearchBoardsForUserInTeam(teamID, term, userID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("SearchBoards", + mlog.String("teamID", teamID), + mlog.Int("boardsCount", len(boards)), + ) + + data, err := json.Marshal(boards) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + // response + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.AddMeta("boardsCount", len(boards)) + auditRec.Success() +} + +func (a *API) handleSearchLinkableBoards(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /teams/{teamID}/boards/search/linkable searchLinkableBoards + // + // Returns the boards that match with a search term in the team and the + // user has permission to manage members + // + // --- + // produces: + // - application/json + // parameters: + // - name: teamID + // in: path + // description: Team ID + // required: true + // type: string + // - name: q + // in: query + // description: The search term. Must have at least one character + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // type: array + // items: + // "$ref": "#/definitions/Board" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + if !a.MattermostAuth { + a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in standalone mode", nil) + return + } + + teamID := mux.Vars(r)["teamID"] + term := r.URL.Query().Get("q") + userID := getUserID(r) + + if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"}) + return + } + + if len(term) == 0 { + jsonStringResponse(w, http.StatusOK, "[]") + return + } + + auditRec := a.makeAuditRecord(r, "searchLinkableBoards", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + auditRec.AddMeta("teamID", teamID) + + // retrieve boards list + boards, err := a.app.SearchBoardsForUserInTeam(teamID, term, userID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + linkableBoards := []*model.Board{} + for _, board := range boards { + if a.permissions.HasPermissionToBoard(userID, board.ID, model.PermissionManageBoardRoles) { + linkableBoards = append(linkableBoards, board) + } + } + + a.logger.Debug("SearchLinkableBoards", + mlog.String("teamID", teamID), + mlog.Int("boardsCount", len(linkableBoards)), + ) + + data, err := json.Marshal(linkableBoards) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + // response + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.AddMeta("boardsCount", len(linkableBoards)) + auditRec.Success() +} + +func (a *API) handleSearchAllBoards(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /boards/search searchBoards + // + // Returns the boards that match with a search term + // + // --- + // produces: + // - application/json + // parameters: + // - name: q + // in: query + // description: The search term. Must have at least one character + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // type: array + // items: + // "$ref": "#/definitions/Board" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + term := r.URL.Query().Get("q") + userID := getUserID(r) + + if len(term) == 0 { + jsonStringResponse(w, http.StatusOK, "[]") + return + } + + auditRec := a.makeAuditRecord(r, "searchAllBoards", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + + // retrieve boards list + boards, err := a.app.SearchBoardsForUser(term, userID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("SearchAllBoards", + mlog.Int("boardsCount", len(boards)), + ) + + data, err := json.Marshal(boards) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + // response + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.AddMeta("boardsCount", len(boards)) + auditRec.Success() +} diff --git a/server/api/sharing.go b/server/api/sharing.go new file mode 100644 index 000000000..9c0a8f96b --- /dev/null +++ b/server/api/sharing.go @@ -0,0 +1,181 @@ +package api + +import ( + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/gorilla/mux" + "github.com/mattermost/focalboard/server/model" + "github.com/mattermost/focalboard/server/services/audit" + + "github.com/mattermost/mattermost-server/v6/shared/mlog" +) + +func (a *API) registerSharingRoutes(r *mux.Router) { + // Sharing APIs + r.HandleFunc("/boards/{boardID}/sharing", a.sessionRequired(a.handlePostSharing)).Methods("POST") + r.HandleFunc("/boards/{boardID}/sharing", a.sessionRequired(a.handleGetSharing)).Methods("GET") +} + +func (a *API) handleGetSharing(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /boards/{boardID}/sharing getSharing + // + // Returns sharing information for a board + // + // --- + // produces: + // - application/json + // parameters: + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // "$ref": "#/definitions/Sharing" + // '404': + // description: board not found + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + vars := mux.Vars(r) + boardID := vars["boardID"] + + userID := getUserID(r) + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionShareBoard) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to sharing the board"}) + return + } + + auditRec := a.makeAuditRecord(r, "getSharing", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + auditRec.AddMeta("boardID", boardID) + + sharing, err := a.app.GetSharing(boardID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + if sharing == nil { + jsonStringResponse(w, http.StatusOK, "") + return + } + + sharingData, err := json.Marshal(sharing) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + jsonBytesResponse(w, http.StatusOK, sharingData) + + a.logger.Debug("GET sharing", + mlog.String("boardID", boardID), + mlog.String("shareID", sharing.ID), + mlog.Bool("enabled", sharing.Enabled), + ) + auditRec.AddMeta("shareID", sharing.ID) + auditRec.AddMeta("enabled", sharing.Enabled) + auditRec.Success() +} + +func (a *API) handlePostSharing(w http.ResponseWriter, r *http.Request) { + // swagger:operation POST /boards/{boardID}/sharing postSharing + // + // Sets sharing information for a board + // + // --- + // produces: + // - application/json + // parameters: + // - name: boardID + // in: path + // description: Board ID + // required: true + // type: string + // - name: Body + // in: body + // description: sharing information for a root block + // required: true + // schema: + // "$ref": "#/definitions/Sharing" + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + boardID := mux.Vars(r)["boardID"] + + userID := getUserID(r) + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionShareBoard) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to sharing the board"}) + return + } + + requestBody, err := ioutil.ReadAll(r.Body) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + var sharing model.Sharing + err = json.Unmarshal(requestBody, &sharing) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + // Stamp boardID from the URL + sharing.ID = boardID + + auditRec := a.makeAuditRecord(r, "postSharing", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + auditRec.AddMeta("shareID", sharing.ID) + auditRec.AddMeta("enabled", sharing.Enabled) + + // Stamp ModifiedBy + modifiedBy := userID + if userID == model.SingleUser { + modifiedBy = "" + } + sharing.ModifiedBy = modifiedBy + + if userID == model.SingleUser { + userID = "" + } + + if !a.app.GetClientConfig().EnablePublicSharedBoards { + a.logger.Warn( + "Attempt to turn on sharing for board via API failed, sharing off in configuration.", + mlog.String("boardID", sharing.ID), + mlog.String("userID", userID)) + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "Turning on sharing for board failed, see log for details.", nil) + return + } + + sharing.ModifiedBy = userID + + err = a.app.UpsertSharing(sharing) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + jsonStringResponse(w, http.StatusOK, "{}") + + a.logger.Debug("POST sharing", mlog.String("sharingID", sharing.ID)) + auditRec.Success() +} diff --git a/server/api/subscriptions.go b/server/api/subscriptions.go new file mode 100644 index 000000000..5c2052442 --- /dev/null +++ b/server/api/subscriptions.go @@ -0,0 +1,236 @@ +package api + +import ( + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/gorilla/mux" + "github.com/mattermost/focalboard/server/model" + "github.com/mattermost/focalboard/server/services/audit" + + "github.com/mattermost/mattermost-server/v6/shared/mlog" +) + +func (a *API) registerSubscriptionsRoutes(r *mux.Router) { + // Subscription APIs + r.HandleFunc("/subscriptions", a.sessionRequired(a.handleCreateSubscription)).Methods("POST") + r.HandleFunc("/subscriptions/{blockID}/{subscriberID}", a.sessionRequired(a.handleDeleteSubscription)).Methods("DELETE") + r.HandleFunc("/subscriptions/{subscriberID}", a.sessionRequired(a.handleGetSubscriptions)).Methods("GET") +} + +// subscriptions + +func (a *API) handleCreateSubscription(w http.ResponseWriter, r *http.Request) { + // swagger:operation POST /subscriptions createSubscription + // + // Creates a subscription to a block for a user. The user will receive change notifications for the block. + // + // --- + // produces: + // - application/json + // parameters: + // - name: Body + // in: body + // description: subscription definition + // required: true + // schema: + // "$ref": "#/definitions/Subscription" + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // "$ref": "#/definitions/User" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + requestBody, err := ioutil.ReadAll(r.Body) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + var sub model.Subscription + + err = json.Unmarshal(requestBody, &sub) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + if err = sub.IsValid(); err != nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) + } + + ctx := r.Context() + session := ctx.Value(sessionContextKey).(*model.Session) + + auditRec := a.makeAuditRecord(r, "createSubscription", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + auditRec.AddMeta("subscriber_id", sub.SubscriberID) + auditRec.AddMeta("block_id", sub.BlockID) + + // User can only create subscriptions for themselves (for now) + if session.UserID != sub.SubscriberID { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "userID and subscriberID mismatch", nil) + return + } + + // check for valid block + block, err := a.app.GetBlockByID(sub.BlockID) + if err != nil || block == nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "invalid blockID", err) + return + } + + subNew, err := a.app.CreateSubscription(&sub) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("CREATE subscription", + mlog.String("subscriber_id", subNew.SubscriberID), + mlog.String("block_id", subNew.BlockID), + ) + + json, err := json.Marshal(subNew) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + jsonBytesResponse(w, http.StatusOK, json) + auditRec.Success() +} + +func (a *API) handleDeleteSubscription(w http.ResponseWriter, r *http.Request) { + // swagger:operation DELETE /subscriptions/{blockID}/{subscriberID} deleteSubscription + // + // Deletes a subscription a user has for a a block. The user will no longer receive change notifications for the block. + // + // --- + // produces: + // - application/json + // parameters: + // - name: blockID + // in: path + // description: Block ID + // required: true + // type: string + // - name: subscriberID + // in: path + // description: Subscriber ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + ctx := r.Context() + session := ctx.Value(sessionContextKey).(*model.Session) + + vars := mux.Vars(r) + blockID := vars["blockID"] + subscriberID := vars["subscriberID"] + + auditRec := a.makeAuditRecord(r, "deleteSubscription", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + auditRec.AddMeta("block_id", blockID) + auditRec.AddMeta("subscriber_id", subscriberID) + + // User can only delete subscriptions for themselves + if session.UserID != subscriberID { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "access denied", nil) + return + } + + _, err := a.app.DeleteSubscription(blockID, subscriberID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("DELETE subscription", + mlog.String("blockID", blockID), + mlog.String("subscriberID", subscriberID), + ) + jsonStringResponse(w, http.StatusOK, "{}") + + auditRec.Success() +} + +func (a *API) handleGetSubscriptions(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /subscriptions/{subscriberID} getSubscriptions + // + // Gets subscriptions for a user. + // + // --- + // produces: + // - application/json + // parameters: + // - name: subscriberID + // in: path + // description: Subscriber ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // type: array + // items: + // "$ref": "#/definitions/User" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + ctx := r.Context() + session := ctx.Value(sessionContextKey).(*model.Session) + + vars := mux.Vars(r) + subscriberID := vars["subscriberID"] + + auditRec := a.makeAuditRecord(r, "getSubscriptions", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + auditRec.AddMeta("subscriber_id", subscriberID) + + // User can only get subscriptions for themselves (for now) + if session.UserID != subscriberID { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "access denied", nil) + return + } + + subs, err := a.app.GetSubscriptions(subscriberID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + a.logger.Debug("GET subscriptions", + mlog.String("subscriberID", subscriberID), + mlog.Int("count", len(subs)), + ) + + json, err := json.Marshal(subs) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + jsonBytesResponse(w, http.StatusOK, json) + + auditRec.AddMeta("subscription_count", len(subs)) + auditRec.Success() +} diff --git a/server/api/system.go b/server/api/system.go new file mode 100644 index 000000000..83a2cd733 --- /dev/null +++ b/server/api/system.go @@ -0,0 +1,26 @@ +package api + +import ( + "net/http" + + "github.com/gorilla/mux" +) + +func (a *API) registerSystemRoutes(r *mux.Router) { + // System APIs + r.HandleFunc("/hello", a.handleHello).Methods("GET") +} + +func (a *API) handleHello(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /hello hello + // + // Responds with `Hello` if the web service is running. + // + // --- + // produces: + // - text/plain + // responses: + // '200': + // description: success + stringResponse(w, "Hello") +} diff --git a/server/api/teams.go b/server/api/teams.go new file mode 100644 index 000000000..884cd0b21 --- /dev/null +++ b/server/api/teams.go @@ -0,0 +1,245 @@ +package api + +import ( + "encoding/json" + "net/http" + + "github.com/gorilla/mux" + "github.com/mattermost/focalboard/server/model" + "github.com/mattermost/focalboard/server/services/audit" + "github.com/mattermost/focalboard/server/utils" +) + +func (a *API) registerTeamsRoutes(r *mux.Router) { + // Team APIs + r.HandleFunc("/teams", a.sessionRequired(a.handleGetTeams)).Methods("GET") + r.HandleFunc("/teams/{teamID}", a.sessionRequired(a.handleGetTeam)).Methods("GET") + r.HandleFunc("/teams/{teamID}/users", a.sessionRequired(a.handleGetTeamUsers)).Methods("GET") + r.HandleFunc("/teams/{teamID}/archive/export", a.sessionRequired(a.handleArchiveExportTeam)).Methods("GET") +} + +func (a *API) handleGetTeams(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /teams getTeams + // + // Returns information of all the teams + // + // --- + // produces: + // - application/json + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // type: array + // items: + // "$ref": "#/definitions/Team" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + userID := getUserID(r) + + teams, err := a.app.GetTeamsForUser(userID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + } + + auditRec := a.makeAuditRecord(r, "getTeams", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + auditRec.AddMeta("teamCount", len(teams)) + + data, err := json.Marshal(teams) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + jsonBytesResponse(w, http.StatusOK, data) + auditRec.Success() +} + +func (a *API) handleGetTeam(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /teams/{teamID} getTeam + // + // Returns information of the root team + // + // --- + // produces: + // - application/json + // parameters: + // - name: teamID + // in: path + // description: Team ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // "$ref": "#/definitions/Team" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + vars := mux.Vars(r) + teamID := vars["teamID"] + userID := getUserID(r) + + if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"}) + return + } + + var team *model.Team + var err error + + if a.MattermostAuth { + team, err = a.app.GetTeam(teamID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + } + if team == nil { + a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "invalid team", nil) + return + } + } else { + team, err = a.app.GetRootTeam() + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + } + + auditRec := a.makeAuditRecord(r, "getTeam", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + auditRec.AddMeta("resultTeamID", team.ID) + + data, err := json.Marshal(team) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + jsonBytesResponse(w, http.StatusOK, data) + auditRec.Success() +} + +func (a *API) handlePostTeamRegenerateSignupToken(w http.ResponseWriter, r *http.Request) { + // swagger:operation POST /teams/{teamID}/regenerate_signup_token regenerateSignupToken + // + // Regenerates the signup token for the root team + // + // --- + // produces: + // - application/json + // parameters: + // - name: teamID + // in: path + // description: Team ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + if a.MattermostAuth { + a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil) + return + } + + team, err := a.app.GetRootTeam() + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + auditRec := a.makeAuditRecord(r, "regenerateSignupToken", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + + team.SignupToken = utils.NewID(utils.IDTypeToken) + + err = a.app.UpsertTeamSignupToken(*team) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + jsonStringResponse(w, http.StatusOK, "{}") + auditRec.Success() +} + +func (a *API) handleGetTeamUsers(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /teams/{teamID}/users getTeamUsers + // + // Returns team users + // + // --- + // produces: + // - application/json + // parameters: + // - name: teamID + // in: path + // description: Team ID + // required: true + // type: string + // - name: search + // in: query + // description: string to filter users list + // required: false + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // type: array + // items: + // "$ref": "#/definitions/User" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + vars := mux.Vars(r) + teamID := vars["teamID"] + userID := getUserID(r) + query := r.URL.Query() + searchQuery := query.Get("search") + + if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "Access denied to team", PermissionError{"access denied to team"}) + return + } + + auditRec := a.makeAuditRecord(r, "getUsers", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + + users, err := a.app.SearchTeamUsers(teamID, searchQuery) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "searchQuery="+searchQuery, err) + return + } + + data, err := json.Marshal(users) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.AddMeta("userCount", len(users)) + auditRec.Success() +} diff --git a/server/api/templates.go b/server/api/templates.go new file mode 100644 index 000000000..8082708ee --- /dev/null +++ b/server/api/templates.go @@ -0,0 +1,90 @@ +package api + +import ( + "encoding/json" + "net/http" + + "github.com/gorilla/mux" + "github.com/mattermost/focalboard/server/model" + "github.com/mattermost/focalboard/server/services/audit" + + "github.com/mattermost/mattermost-server/v6/shared/mlog" +) + +func (a *API) registerTemplatesRoutes(r *mux.Router) { + r.HandleFunc("/teams/{teamID}/templates", a.sessionRequired(a.handleGetTemplates)).Methods("GET") +} + +func (a *API) handleGetTemplates(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /teams/{teamID}/templates getTemplates + // + // Returns team templates + // + // --- + // produces: + // - application/json + // parameters: + // - name: teamID + // in: path + // description: Team ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // type: array + // items: + // "$ref": "#/definitions/Board" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + teamID := mux.Vars(r)["teamID"] + userID := getUserID(r) + + if teamID != model.GlobalTeamID && !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"}) + return + } + + auditRec := a.makeAuditRecord(r, "getTemplates", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + auditRec.AddMeta("teamID", teamID) + + // retrieve boards list + boards, err := a.app.GetTemplateBoards(teamID, userID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + results := []*model.Board{} + for _, board := range boards { + if board.Type == model.BoardTypeOpen { + results = append(results, board) + } else if a.permissions.HasPermissionToBoard(userID, board.ID, model.PermissionViewBoard) { + results = append(results, board) + } + } + + a.logger.Debug("GetTemplates", + mlog.String("teamID", teamID), + mlog.Int("boardsCount", len(results)), + ) + + data, err := json.Marshal(results) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + // response + jsonBytesResponse(w, http.StatusOK, data) + + auditRec.AddMeta("templatesCount", len(results)) + auditRec.Success() +} diff --git a/server/api/users.go b/server/api/users.go new file mode 100644 index 000000000..8af4d2b33 --- /dev/null +++ b/server/api/users.go @@ -0,0 +1,304 @@ +package api + +import ( + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/gorilla/mux" + "github.com/mattermost/focalboard/server/model" + "github.com/mattermost/focalboard/server/services/audit" + "github.com/mattermost/focalboard/server/utils" +) + +func (a *API) registerUsersRoutes(r *mux.Router) { + // Users APIs + r.HandleFunc("/users", a.sessionRequired(a.handleGetUsersList)).Methods("POST") + r.HandleFunc("/users/me", a.sessionRequired(a.handleGetMe)).Methods("GET") + r.HandleFunc("/users/me/memberships", a.sessionRequired(a.handleGetMyMemberships)).Methods("GET") + r.HandleFunc("/users/{userID}", a.sessionRequired(a.handleGetUser)).Methods("GET") + r.HandleFunc("/users/{userID}/config", a.sessionRequired(a.handleUpdateUserConfig)).Methods(http.MethodPut) +} + +func (a *API) handleGetUsersList(w http.ResponseWriter, r *http.Request) { + // swagger:operation POST /users getUser + // + // Returns a user[] + // + // --- + // produces: + // - application/json + // parameters: + // - name: userID + // in: path + // description: User ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // "$ref": "#/definitions/User" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + requestBody, err := ioutil.ReadAll(r.Body) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + var userIDs []string + if err = json.Unmarshal(requestBody, &userIDs); err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + auditRec := a.makeAuditRecord(r, "getUsersList", audit.Fail) + defer a.audit.LogRecord(audit.LevelAuth, auditRec) + + users, err := a.app.GetUsersList(userIDs) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, err.Error(), err) + return + } + + usersList, err := json.Marshal(users) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err) + return + } + + jsonStringResponse(w, http.StatusOK, string(usersList)) + auditRec.Success() +} + +func (a *API) handleGetMe(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /users/me getMe + // + // Returns the currently logged-in user + // + // --- + // produces: + // - application/json + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // "$ref": "#/definitions/User" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + userID := getUserID(r) + + var user *model.User + var err error + + auditRec := a.makeAuditRecord(r, "getMe", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + + if userID == model.SingleUser { + ws, _ := a.app.GetRootTeam() + now := utils.GetMillis() + user = &model.User{ + ID: model.SingleUser, + Username: model.SingleUser, + Email: model.SingleUser, + CreateAt: ws.UpdateAt, + UpdateAt: now, + Props: map[string]interface{}{}, + } + } else { + user, err = a.app.GetUser(userID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + } + + userData, err := json.Marshal(user) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + jsonBytesResponse(w, http.StatusOK, userData) + + auditRec.AddMeta("userID", user.ID) + auditRec.Success() +} + +func (a *API) handleGetMyMemberships(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /users/me/memberships getMyMemberships + // + // Returns the currently users board memberships + // + // --- + // produces: + // - application/json + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // type: array + // items: + // "$ref": "#/definitions/BoardMember" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + userID := getUserID(r) + + auditRec := a.makeAuditRecord(r, "getMyBoardMemberships", audit.Fail) + auditRec.AddMeta("userID", userID) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + + members, err := a.app.GetMembersForUser(userID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + membersData, err := json.Marshal(members) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + jsonBytesResponse(w, http.StatusOK, membersData) + + auditRec.Success() +} + +func (a *API) handleGetUser(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /users/{userID} getUser + // + // Returns a user + // + // --- + // produces: + // - application/json + // parameters: + // - name: userID + // in: path + // description: User ID + // required: true + // type: string + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // schema: + // "$ref": "#/definitions/User" + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + vars := mux.Vars(r) + userID := vars["userID"] + + auditRec := a.makeAuditRecord(r, "postBlocks", audit.Fail) + defer a.audit.LogRecord(audit.LevelRead, auditRec) + auditRec.AddMeta("userID", userID) + + user, err := a.app.GetUser(userID) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + userData, err := json.Marshal(user) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + jsonBytesResponse(w, http.StatusOK, userData) + auditRec.Success() +} + +func (a *API) handleUpdateUserConfig(w http.ResponseWriter, r *http.Request) { + // swagger:operation PATCH /users/{userID}/config updateUserConfig + // + // Updates user config + // + // --- + // produces: + // - application/json + // parameters: + // - name: userID + // in: path + // description: User ID + // required: true + // type: string + // - name: Body + // in: body + // description: User config patch to apply + // required: true + // schema: + // "$ref": "#/definitions/UserPropPatch" + // security: + // - BearerAuth: [] + // responses: + // '200': + // description: success + // default: + // description: internal error + // schema: + // "$ref": "#/definitions/ErrorResponse" + + requestBody, err := ioutil.ReadAll(r.Body) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + var patch *model.UserPropPatch + err = json.Unmarshal(requestBody, &patch) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + vars := mux.Vars(r) + userID := vars["userID"] + + ctx := r.Context() + session := ctx.Value(sessionContextKey).(*model.Session) + + auditRec := a.makeAuditRecord(r, "updateUserConfig", audit.Fail) + defer a.audit.LogRecord(audit.LevelModify, auditRec) + + // a user can update only own config + if userID != session.UserID { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", nil) + return + } + + updatedConfig, err := a.app.UpdateUserConfig(userID, *patch) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + data, err := json.Marshal(updatedConfig) + if err != nil { + a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) + return + } + + jsonBytesResponse(w, http.StatusOK, data) + auditRec.Success() +} diff --git a/server/app/auth.go b/server/app/auth.go index 46cf7474a..598f71901 100644 --- a/server/app/auth.go +++ b/server/app/auth.go @@ -64,6 +64,18 @@ func (a *App) GetUser(id string) (*model.User, error) { return user, nil } +func (a *App) GetUsersList(userIDs []string) ([]*model.User, error) { + if len(userIDs) == 0 { + return nil, errors.New("No User IDs") + } + + users, err := a.store.GetUsersList(userIDs) + if err != nil { + return nil, errors.Wrap(err, "unable to find users") + } + return users, nil +} + // Login create a new user session if the authentication data is valid. func (a *App) Login(username, email, password, mfaToken string) (string, error) { var user *model.User diff --git a/server/app/blocks.go b/server/app/blocks.go index 469da84bc..aa67662c2 100644 --- a/server/app/blocks.go +++ b/server/app/blocks.go @@ -65,10 +65,6 @@ func (a *App) DuplicateBlock(boardID string, blockID string, userID string, asTe return blocks, err } -func (a *App) GetBlocksWithBoardID(boardID string) ([]model.Block, error) { - return a.store.GetBlocksWithBoardID(boardID) -} - func (a *App) PatchBlock(blockID string, blockPatch *model.BlockPatch, modifiedByID string) error { oldBlock, err := a.store.GetBlock(blockID) if err != nil { diff --git a/server/app/boards.go b/server/app/boards.go index 1e6b34fe0..785039b16 100644 --- a/server/app/boards.go +++ b/server/app/boards.go @@ -145,6 +145,36 @@ func (a *App) getBoardDescendantModifiedInfo(boardID string, latest bool) (int64 return timestamp, modifiedBy, nil } +func (a *App) setBoardCategoryFromSource(sourceBoardID, destinationBoardID, userID, teamID string) error { + // find source board's category ID for the user + userCategoryBoards, err := a.GetUserCategoryBoards(userID, teamID) + if err != nil { + return err + } + + var destinationCategoryID string + + for _, categoryBoard := range userCategoryBoards { + for _, boardID := range categoryBoard.BoardIDs { + if boardID == sourceBoardID { + // category found! + destinationCategoryID = categoryBoard.ID + break + } + } + } + + // if source board is not mapped to a category for this user, + // then we have nothing more to do. + if destinationCategoryID == "" { + return nil + } + + // now that we have source board's category, + // we send destination board to the same category + return a.AddUpdateUserCategoryBoard(teamID, userID, destinationCategoryID, destinationBoardID) +} + func (a *App) DuplicateBoard(boardID, userID, toTeam string, asTemplate bool) (*model.BoardsAndBlocks, []*model.BoardMember, error) { bab, members, err := a.store.DuplicateBoard(boardID, userID, toTeam, asTemplate) if err != nil { @@ -156,6 +186,12 @@ func (a *App) DuplicateBoard(boardID, userID, toTeam string, asTemplate bool) (* a.logger.Error("Could not copy files while duplicating board", mlog.String("BoardID", boardID), mlog.Err(err)) } + for _, board := range bab.Boards { + if categoryErr := a.setBoardCategoryFromSource(boardID, board.ID, userID, board.TeamID); categoryErr != nil { + return nil, nil, categoryErr + } + } + // bab.Blocks now has updated file ids for any blocks containing files. We need to store them. blockIDs := make([]string, 0) blockPatches := make([]model.BlockPatch, 0) @@ -247,7 +283,15 @@ func (a *App) CreateBoard(board *model.Board, userID string, addMember bool) (*m a.blockChangeNotifier.Enqueue(func() error { a.wsAdapter.BroadcastBoardChange(newBoard.TeamID, newBoard) - if addMember { + if newBoard.ChannelID != "" { + members, err := a.GetMembersForBoard(board.ID) + if err != nil { + a.logger.Error("Unable to get the board members", mlog.Err(err)) + } + for _, member := range members { + a.wsAdapter.BroadcastMemberChange(newBoard.TeamID, member.BoardID, member) + } + } else if addMember { a.wsAdapter.BroadcastMemberChange(newBoard.TeamID, newBoard.ID, member) } return nil @@ -257,6 +301,14 @@ func (a *App) CreateBoard(board *model.Board, userID string, addMember bool) (*m } func (a *App) PatchBoard(patch *model.BoardPatch, boardID, userID string) (*model.Board, error) { + var oldMembers []*model.BoardMember + if patch.ChannelID != nil && *patch.ChannelID == "" { + var err error + oldMembers, err = a.GetMembersForBoard(boardID) + if err != nil { + a.logger.Error("Unable to get the board members", mlog.Err(err)) + } + } updatedBoard, err := a.store.PatchBoard(boardID, patch, userID) if err != nil { return nil, err @@ -264,6 +316,23 @@ func (a *App) PatchBoard(patch *model.BoardPatch, boardID, userID string) (*mode a.blockChangeNotifier.Enqueue(func() error { a.wsAdapter.BroadcastBoardChange(updatedBoard.TeamID, updatedBoard) + if patch.ChannelID != nil && *patch.ChannelID != "" { + members, err := a.GetMembersForBoard(updatedBoard.ID) + if err != nil { + a.logger.Error("Unable to get the board members", mlog.Err(err)) + } + for _, member := range members { + if member.Synthetic { + a.wsAdapter.BroadcastMemberChange(updatedBoard.TeamID, member.BoardID, member) + } + } + } else if patch.ChannelID != nil && *patch.ChannelID == "" { + for _, oldMember := range oldMembers { + if oldMember.Synthetic { + a.wsAdapter.BroadcastMemberDelete(updatedBoard.TeamID, boardID, oldMember.UserID) + } + } + } return nil }) @@ -326,7 +395,7 @@ func (a *App) AddMemberToBoard(member *model.BoardMember) (*model.BoardMember, e return nil, err } - if existingMembership != nil { + if existingMembership != nil && !existingMembership.Synthetic { return existingMembership, nil } @@ -433,7 +502,11 @@ func (a *App) DeleteBoardMember(boardID, userID string) error { } a.blockChangeNotifier.Enqueue(func() error { - a.wsAdapter.BroadcastMemberDelete(board.TeamID, boardID, userID) + if synteticMember, _ := a.store.GetMemberForBoard(boardID, userID); synteticMember != nil { + a.wsAdapter.BroadcastMemberChange(board.TeamID, boardID, synteticMember) + } else { + a.wsAdapter.BroadcastMemberDelete(board.TeamID, boardID, userID) + } return nil }) diff --git a/server/app/boards_test.go b/server/app/boards_test.go new file mode 100644 index 000000000..0b70dda1b --- /dev/null +++ b/server/app/boards_test.go @@ -0,0 +1,107 @@ +package app + +import ( + "testing" + + "github.com/mattermost/focalboard/server/model" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestAddMemberToBoard(t *testing.T) { + th, tearDown := SetupTestHelper(t) + defer tearDown() + + t.Run("base case", func(t *testing.T) { + const boardID = "board_id_1" + const userID = "user_id_1" + + boardMember := &model.BoardMember{ + BoardID: boardID, + UserID: userID, + SchemeEditor: true, + } + + th.Store.EXPECT().GetBoard(boardID).Return(&model.Board{ + TeamID: "team_id_1", + }, nil) + + th.Store.EXPECT().GetMemberForBoard(boardID, userID).Return(nil, nil) + + th.Store.EXPECT().SaveMember(mock.MatchedBy(func(i interface{}) bool { + p := i.(*model.BoardMember) + return p.BoardID == boardID && p.UserID == userID + })).Return(&model.BoardMember{ + BoardID: boardID, + }, nil) + + // for WS change broadcast + th.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil) + + addedBoardMember, err := th.App.AddMemberToBoard(boardMember) + require.NoError(t, err) + require.Equal(t, boardID, addedBoardMember.BoardID) + }) + + t.Run("return existing non-synthetic membership if any", func(t *testing.T) { + const boardID = "board_id_1" + const userID = "user_id_1" + + boardMember := &model.BoardMember{ + BoardID: boardID, + UserID: userID, + SchemeEditor: true, + } + + th.Store.EXPECT().GetBoard(boardID).Return(&model.Board{ + TeamID: "team_id_1", + }, nil) + + th.Store.EXPECT().GetMemberForBoard(boardID, userID).Return(&model.BoardMember{ + UserID: userID, + BoardID: boardID, + Synthetic: false, + }, nil) + + addedBoardMember, err := th.App.AddMemberToBoard(boardMember) + require.NoError(t, err) + require.Equal(t, boardID, addedBoardMember.BoardID) + }) + + t.Run("should convert synthetic membership into natural membership", func(t *testing.T) { + const boardID = "board_id_1" + const userID = "user_id_1" + + boardMember := &model.BoardMember{ + BoardID: boardID, + UserID: userID, + SchemeEditor: true, + } + + th.Store.EXPECT().GetBoard(boardID).Return(&model.Board{ + TeamID: "team_id_1", + }, nil) + + th.Store.EXPECT().GetMemberForBoard(boardID, userID).Return(&model.BoardMember{ + UserID: userID, + BoardID: boardID, + Synthetic: true, + }, nil) + + th.Store.EXPECT().SaveMember(mock.MatchedBy(func(i interface{}) bool { + p := i.(*model.BoardMember) + return p.BoardID == boardID && p.UserID == userID + })).Return(&model.BoardMember{ + UserID: userID, + BoardID: boardID, + Synthetic: false, + }, nil) + + // for WS change broadcast + th.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil) + + addedBoardMember, err := th.App.AddMemberToBoard(boardMember) + require.NoError(t, err) + require.Equal(t, boardID, addedBoardMember.BoardID) + }) +} diff --git a/server/app/export.go b/server/app/export.go index 9135e3e0b..453309055 100644 --- a/server/app/export.go +++ b/server/app/export.go @@ -82,7 +82,7 @@ func (a *App) writeArchiveBoard(zw *zip.Writer, board model.Board, opt model.Exp var files []string // write the board's blocks // TODO: paginate this - blocks, err := a.GetBlocksWithBoardID(board.ID) + blocks, err := a.GetBlocksForBoard(board.ID) if err != nil { return err } diff --git a/server/app/insights.go b/server/app/insights.go new file mode 100644 index 000000000..bdd62400d --- /dev/null +++ b/server/app/insights.go @@ -0,0 +1,78 @@ +package app + +import ( + "github.com/mattermost/focalboard/server/model" + mmModel "github.com/mattermost/mattermost-server/v6/model" + "github.com/pkg/errors" +) + +func (a *App) GetTeamBoardsInsights(userID string, teamID string, opts *mmModel.InsightsOpts) (*model.BoardInsightsList, error) { + // check if server is properly licensed, and user is not a guest + userPermitted, err := insightPermissionGate(a, userID) + if err != nil { + return nil, err + } + if !userPermitted { + return nil, errors.New("User isn't authorized to access insights.") + } + boardIDs, err := getUserBoards(userID, teamID, a) + if err != nil { + return nil, err + } + return a.store.GetTeamBoardsInsights(teamID, userID, opts.StartUnixMilli, opts.Page*opts.PerPage, opts.PerPage, boardIDs) +} + +func (a *App) GetUserBoardsInsights(userID string, teamID string, opts *mmModel.InsightsOpts) (*model.BoardInsightsList, error) { + // check if server is properly licensed, and user is not a guest + userPermitted, err := insightPermissionGate(a, userID) + if err != nil { + return nil, err + } + if !userPermitted { + return nil, errors.New("User isn't authorized to access insights.") + } + boardIDs, err := getUserBoards(userID, teamID, a) + if err != nil { + return nil, err + } + return a.store.GetUserBoardsInsights(teamID, userID, opts.StartUnixMilli, opts.Page*opts.PerPage, opts.PerPage, boardIDs) +} + +func insightPermissionGate(a *App, userID string) (bool, error) { + licenseError := errors.New("invalid license/authorization to use insights API") + guestError := errors.New("guests aren't authorized to use insights API") + lic := a.store.GetLicense() + if lic == nil { + a.logger.Debug("Deployment doesn't have a license") + return false, licenseError + } + user, err := a.store.GetUserByID(userID) + if err != nil { + return false, err + } + if lic.SkuShortName != mmModel.LicenseShortSkuProfessional && lic.SkuShortName != mmModel.LicenseShortSkuEnterprise { + return false, licenseError + } + if user.IsGuest { + return false, guestError + } + return true, nil +} + +func (a *App) GetUserTimezone(userID string) (string, error) { + return a.store.GetUserTimezone(userID) +} + +func getUserBoards(userID string, teamID string, a *App) ([]string, error) { + // get boards accessible by user and filter boardIDs + boards, err := a.store.GetBoardsForUserAndTeam(userID, teamID) + if err != nil { + return nil, errors.New("error getting boards for user") + } + boardIDs := make([]string, 0, len(boards)) + + for _, board := range boards { + boardIDs = append(boardIDs, board.ID) + } + return boardIDs, nil +} diff --git a/server/app/insights_test.go b/server/app/insights_test.go new file mode 100644 index 000000000..95f9bc7fe --- /dev/null +++ b/server/app/insights_test.go @@ -0,0 +1,89 @@ +package app + +import ( + "testing" + + "github.com/mattermost/focalboard/server/model" + mmModel "github.com/mattermost/mattermost-server/v6/model" + "github.com/stretchr/testify/require" +) + +var mockInsightsBoards = []*model.Board{ + { + ID: "mock-user-workspace-id", + Title: "MockUserWorkspace", + }, +} + +var mockTeamInsights = []*model.BoardInsight{ + { + BoardID: "board-id-1", + }, + { + BoardID: "board-id-2", + }, +} + +var mockTeamInsightsList = &model.BoardInsightsList{ + InsightsListData: mmModel.InsightsListData{HasNext: false}, + Items: mockTeamInsights, +} + +type insightError struct { + msg string +} + +func (ie insightError) Error() string { + return ie.msg +} + +func TestGetTeamAndUserBoardsInsights(t *testing.T) { + th, tearDown := SetupTestHelper(t) + defer tearDown() + + t.Run("success query", func(t *testing.T) { + fakeLicense := &mmModel.License{Features: &mmModel.Features{}, SkuShortName: mmModel.LicenseShortSkuEnterprise} + th.Store.EXPECT().GetLicense().Return(fakeLicense).AnyTimes() + fakeUser := &model.User{ + ID: "user-id", + IsGuest: false, + } + th.Store.EXPECT().GetUserByID("user-id").Return(fakeUser, nil).AnyTimes() + th.Store.EXPECT().GetBoardsForUserAndTeam("user-id", "team-id").Return(mockInsightsBoards, nil).AnyTimes() + th.Store.EXPECT(). + GetTeamBoardsInsights("team-id", "user-id", int64(0), 0, 10, []string{"mock-user-workspace-id"}). + Return(mockTeamInsightsList, nil) + results, err := th.App.GetTeamBoardsInsights("user-id", "team-id", &mmModel.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 10}) + require.NoError(t, err) + require.Len(t, results.Items, 2) + th.Store.EXPECT(). + GetUserBoardsInsights("team-id", "user-id", int64(0), 0, 10, []string{"mock-user-workspace-id"}). + Return(mockTeamInsightsList, nil) + results, err = th.App.GetUserBoardsInsights("user-id", "team-id", &mmModel.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 10}) + require.NoError(t, err) + require.Len(t, results.Items, 2) + }) + + t.Run("fail query", func(t *testing.T) { + fakeLicense := &mmModel.License{Features: &mmModel.Features{}, SkuShortName: mmModel.LicenseShortSkuEnterprise} + th.Store.EXPECT().GetLicense().Return(fakeLicense).AnyTimes() + fakeUser := &model.User{ + ID: "user-id", + IsGuest: false, + } + th.Store.EXPECT().GetUserByID("user-id").Return(fakeUser, nil).AnyTimes() + th.Store.EXPECT().GetBoardsForUserAndTeam("user-id", "team-id").Return(mockInsightsBoards, nil).AnyTimes() + th.Store.EXPECT(). + GetTeamBoardsInsights("team-id", "user-id", int64(0), 0, 10, []string{"mock-user-workspace-id"}). + Return(nil, insightError{"board-insight-error"}) + _, err := th.App.GetTeamBoardsInsights("user-id", "team-id", &mmModel.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 10}) + require.Error(t, err) + require.ErrorIs(t, err, insightError{"board-insight-error"}) + th.Store.EXPECT(). + GetUserBoardsInsights("team-id", "user-id", int64(0), 0, 10, []string{"mock-user-workspace-id"}). + Return(nil, insightError{"board-insight-error"}) + _, err = th.App.GetUserBoardsInsights("user-id", "team-id", &mmModel.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 10}) + require.Error(t, err) + require.ErrorIs(t, err, insightError{"board-insight-error"}) + }) +} diff --git a/server/app/onboarding_test.go b/server/app/onboarding_test.go index bc1b27eb6..eb39679b7 100644 --- a/server/app/onboarding_test.go +++ b/server/app/onboarding_test.go @@ -50,6 +50,7 @@ func TestPrepareOnboardingTour(t *testing.T) { } th.Store.EXPECT().PatchUserProps(userID, userPropPatch).Return(nil) + th.Store.EXPECT().GetUserCategoryBoards(userID, "0").Return([]model.CategoryBoards{}, nil) teamID, boardID, err := th.App.PrepareOnboardingTour(userID, teamID) assert.NoError(t, err) @@ -86,6 +87,7 @@ func TestCreateWelcomeBoard(t *testing.T) { } newType := model.BoardTypePrivate th.Store.EXPECT().PatchBoard("board_id_1", &model.BoardPatch{Type: &newType}, "user_id_1").Return(&privateWelcomeBoard, nil) + th.Store.EXPECT().GetUserCategoryBoards(userID, "0") boardID, err := th.App.createWelcomeBoard(userID, teamID) assert.Nil(t, err) diff --git a/server/assets/templates-boardarchive/b7wnw9awd4pnefryhq51apbzb4c/board.jsonl b/server/assets/templates-boardarchive/b7wnw9awd4pnefryhq51apbzb4c/board.jsonl index 9f9b2a540..865388e91 100644 --- a/server/assets/templates-boardarchive/b7wnw9awd4pnefryhq51apbzb4c/board.jsonl +++ b/server/assets/templates-boardarchive/b7wnw9awd4pnefryhq51apbzb4c/board.jsonl @@ -3,7 +3,7 @@ {"type":"block","data":{"id":"chki1tsudciyiiffrkqbcmp71rh","parentId":"b7wnw9awd4pnefryhq51apbzb4c","rootId":"b7wnw9awd4pnefryhq51apbzb4c","createdBy":"mweioqznbife7p7aee7dr4wcxo","modifiedBy":"mweioqznbife7p7aee7dr4wcxo","schema":1,"type":"card","title":"Video production","fields":{"contentOrder":["a9ti13dqo8jfmjdmg97f5umfdyw","717fa85sx3f8f8m81f771s9hmwr","a4se5s4ozx3ry8ec57w6z6jpk7y","7n37rxrn9uffdzrfi1xajotzjey","7ifofmuwjzbdzppfxgtuai4i47h","7cfc4fkpz53gn9frciz9kui4p1c"],"icon":"📹","isTemplate":false,"properties":{"4cf1568d-530f-4028-8ffd-bdc65249187e":"b1abafbf-a038-4a19-8b68-56e0fd2319f7","d777ba3b-8728-40d1-87a6-59406bbbbfb0":"34eb9c25-d5bf-49d9-859e-f74f4e0030e7"}},"createAt":1641497048092,"updateAt":1643788318629,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} {"type":"block","data":{"id":"cmt5usr1mw3fom886t34ekjquay","parentId":"b7wnw9awd4pnefryhq51apbzb4c","rootId":"b7wnw9awd4pnefryhq51apbzb4c","createdBy":"mweioqznbife7p7aee7dr4wcxo","modifiedBy":"mweioqznbife7p7aee7dr4wcxo","schema":1,"type":"card","title":"Offsite plans","fields":{"contentOrder":["aw53ugkfq8pyi9fjh9j6i4kdeiw","7ni9593iz3pnb7xitoz3guwq5gh","agjkcro3x7irbxedyxrn8iuerrr","75zkot1f3sjb7ifysuzijitw91y","7is5m8apdu3g53c8f6cz6sq7bmh","7xsmzscbqn3ftudzqbb4w1q7t7e"],"icon":"🚙","isTemplate":false,"properties":{"4cf1568d-530f-4028-8ffd-bdc65249187e":"8b05c83e-a44a-4d04-831e-97f01d8e2003","d777ba3b-8728-40d1-87a6-59406bbbbfb0":"dabadd9b-adf1-4d9f-8702-805ac6cef602"}},"createAt":1641497048336,"updateAt":1643788318629,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} {"type":"block","data":{"id":"cnqsbzg4b7brfddtyh7fc66atrw","parentId":"b7wnw9awd4pnefryhq51apbzb4c","rootId":"b7wnw9awd4pnefryhq51apbzb4c","createdBy":"mweioqznbife7p7aee7dr4wcxo","modifiedBy":"mweioqznbife7p7aee7dr4wcxo","schema":1,"type":"card","title":"Social Media Strategy","fields":{"contentOrder":["ao57n1fbtmt8q8bfk8ieqgzqt3a","76h9y996sdj8sbrbpqjo9d8cwto","aco8iu5jp7jbyzmzegwxkeusgzr","7y6zcyofmsfrbt899ts1ixr3iey","7hudywfzcwirkpcp1p5jhsfs83r","7jzw67ngdgtns8mstsg9g614oac"],"icon":"🎉","isTemplate":false,"properties":{"4cf1568d-530f-4028-8ffd-bdc65249187e":"b1abafbf-a038-4a19-8b68-56e0fd2319f7","d777ba3b-8728-40d1-87a6-59406bbbbfb0":"d37a61f4-f332-4db9-8b2d-5e0a91aa20ed"}},"createAt":1641497048417,"updateAt":1643788318629,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} -{"type":"block","data":{"id":"vfs8sj79dt7n75bomn46fybxmfo","parentId":"b7wnw9awd4pnefryhq51apbzb4c","rootId":"b7wnw9awd4pnefryhq51apbzb4c","createdBy":"mweioqznbife7p7aee7dr4wcxo","modifiedBy":"mweioqznbife7p7aee7dr4wcxo","schema":1,"type":"view","title":"Discussion Items","fields":{"cardOrder":["cjpkiya33qsagr4f9hrdwhgiajc"],"collapsedOptionIds":[],"columnCalculations":{},"columnWidths":{},"defaultTemplateId":"ch798q5ucefyobf5bymgqjt4f3h","filter":{"filters":[],"operation":"and"},"groupById":"d777ba3b-8728-40d1-87a6-59406bbbbfb0","hiddenOptionIds":[""],"kanbanCalculations":{},"sortOptions":[{"propertyId":"4cf1568d-530f-4028-8ffd-bdc65249187e","reversed":false}],"viewType":"board","visibleOptionIds":[],"visiblePropertyIds":["4cf1568d-530f-4028-8ffd-bdc65249187e"]},"createAt":1641497048501,"updateAt":1643788318629,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} +{"type":"block","data":{"id":"vfs8sj79dt7n75bomn46fybxmfo","parentId":"b7wnw9awd4pnefryhq51apbzb4c","rootId":"b7wnw9awd4pnefryhq51apbzb4c","createdBy":"mweioqznbife7p7aee7dr4wcxo","modifiedBy":"mweioqznbife7p7aee7dr4wcxo","schema":1,"type":"view","title":"Discussion Items","fields":{"cardOrder":["cjpkiya33qsagr4f9hrdwhgiajc"],"collapsedOptionIds":[],"columnCalculations":{},"columnWidths":{},"defaultTemplateId":"","filter":{"filters":[],"operation":"and"},"groupById":"d777ba3b-8728-40d1-87a6-59406bbbbfb0","hiddenOptionIds":[""],"kanbanCalculations":{},"sortOptions":[{"propertyId":"4cf1568d-530f-4028-8ffd-bdc65249187e","reversed":false}],"viewType":"board","visibleOptionIds":[],"visiblePropertyIds":["4cf1568d-530f-4028-8ffd-bdc65249187e"]},"createAt":1641497048501,"updateAt":1643788318629,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} {"type":"block","data":{"id":"73dzfgistnbgzuekc6c8irou9wy","parentId":"cgwagmaw6gin7xcq7nwew8rsynr","rootId":"b7wnw9awd4pnefryhq51apbzb4c","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"checkbox","title":"","fields":{"value":false},"createAt":1641586451774,"updateAt":1643788318632,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} {"type":"block","data":{"id":"7b3njq5m3n78hdpe4bimzr34fic","parentId":"cgwagmaw6gin7xcq7nwew8rsynr","rootId":"b7wnw9awd4pnefryhq51apbzb4c","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"checkbox","title":"","fields":{"value":false},"createAt":1641586448934,"updateAt":1643788318632,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} {"type":"block","data":{"id":"7b7hsbkm6sifqfqi4gstxxaz7my","parentId":"cgwagmaw6gin7xcq7nwew8rsynr","rootId":"b7wnw9awd4pnefryhq51apbzb4c","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"divider","title":"","fields":{},"createAt":1641586358664,"updateAt":1643788318632,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} diff --git a/server/assets/templates-boardarchive/bbn1888mprfrm5fjw9f1je9x3xo/board.jsonl b/server/assets/templates-boardarchive/bbn1888mprfrm5fjw9f1je9x3xo/board.jsonl index d647309a1..82a8c080e 100644 --- a/server/assets/templates-boardarchive/bbn1888mprfrm5fjw9f1je9x3xo/board.jsonl +++ b/server/assets/templates-boardarchive/bbn1888mprfrm5fjw9f1je9x3xo/board.jsonl @@ -5,7 +5,7 @@ {"type":"block","data":{"id":"cx7cki81xppd3pdgnyktwbgtzer","parentId":"bbn1888mprfrm5fjw9f1je9x3xo","rootId":"bbn1888mprfrm5fjw9f1je9x3xo","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"card","title":"Feed Fluffy","fields":{"contentOrder":["as5kdrix3ibd3jrnqzz94dcqqba"],"icon":"🐱","isTemplate":false,"properties":{"a9zf59u8x1rf4ywctpcqama7tio":"an51dnkenmoog9cetapbc4uyt3y"}},"createAt":1640281433850,"updateAt":1643788318630,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} {"type":"block","data":{"id":"czowhma7rnpgb3eczbqo3t7fijo","parentId":"bbn1888mprfrm5fjw9f1je9x3xo","rootId":"bbn1888mprfrm5fjw9f1je9x3xo","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"card","title":"Gardening","fields":{"contentOrder":[],"icon":"🌳","isTemplate":false,"properties":{"a9zf59u8x1rf4ywctpcqama7tio":"afpy8s7i45frggprmfsqngsocqh"}},"createAt":1640281433750,"updateAt":1643788318630,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} {"type":"block","data":{"id":"vjq4piq89kbds5x5zq39zww7joo","parentId":"bbn1888mprfrm5fjw9f1je9x3xo","rootId":"bbn1888mprfrm5fjw9f1je9x3xo","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"view","title":"List View","fields":{"cardOrder":[],"collapsedOptionIds":[],"columnCalculations":{},"columnWidths":{"__title":280},"defaultTemplateId":"","filter":{"filters":[],"operation":"and"},"hiddenOptionIds":[],"kanbanCalculations":{},"sortOptions":[],"viewType":"table","visibleOptionIds":[],"visiblePropertyIds":["a9zf59u8x1rf4ywctpcqama7tio","abthng7baedhhtrwsdodeuincqy"]},"createAt":1641247999081,"updateAt":1643788318630,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} -{"type":"block","data":{"id":"vyeipq97iqbfjtd6fgcbxg6xbme","parentId":"bbn1888mprfrm5fjw9f1je9x3xo","rootId":"bbn1888mprfrm5fjw9f1je9x3xo","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"view","title":"Board View","fields":{"cardOrder":["co6a88h6og3dm3kkub64kyb71jw","c5xamko6rpibhje3bjreenon7ce","cr7gz7sempbfqpq7sign4jaeyxc","cx7cki81xppd3pdgnyktwbgtzer","czowhma7rnpgb3eczbqo3t7fijo"],"collapsedOptionIds":[],"columnCalculations":{},"columnWidths":{},"defaultTemplateId":"cidrrzojxpfroicutox1hoyk91h","filter":{"filters":[],"operation":"and"},"groupById":"a9zf59u8x1rf4ywctpcqama7tio","hiddenOptionIds":[],"kanbanCalculations":{},"sortOptions":[],"viewType":"board","visibleOptionIds":["an51dnkenmoog9cetapbc4uyt3y","afpy8s7i45frggprmfsqngsocqh","aj4jyekqqssatjcq7r7chmy19ey",""],"visiblePropertyIds":["a9zf59u8x1rf4ywctpcqama7tio"]},"createAt":1640281433698,"updateAt":1643788318630,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} +{"type":"block","data":{"id":"vyeipq97iqbfjtd6fgcbxg6xbme","parentId":"bbn1888mprfrm5fjw9f1je9x3xo","rootId":"bbn1888mprfrm5fjw9f1je9x3xo","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"view","title":"Board View","fields":{"cardOrder":["co6a88h6og3dm3kkub64kyb71jw","c5xamko6rpibhje3bjreenon7ce","cr7gz7sempbfqpq7sign4jaeyxc","cx7cki81xppd3pdgnyktwbgtzer","czowhma7rnpgb3eczbqo3t7fijo"],"collapsedOptionIds":[],"columnCalculations":{},"columnWidths":{},"defaultTemplateId":"","filter":{"filters":[],"operation":"and"},"groupById":"a9zf59u8x1rf4ywctpcqama7tio","hiddenOptionIds":[],"kanbanCalculations":{},"sortOptions":[],"viewType":"board","visibleOptionIds":["an51dnkenmoog9cetapbc4uyt3y","afpy8s7i45frggprmfsqngsocqh","aj4jyekqqssatjcq7r7chmy19ey",""],"visiblePropertyIds":["a9zf59u8x1rf4ywctpcqama7tio"]},"createAt":1640281433698,"updateAt":1643788318630,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} {"type":"block","data":{"id":"7fjacjgfxjfrf3psxc46wwsgqdo","parentId":"c5xamko6rpibhje3bjreenon7ce","rootId":"bbn1888mprfrm5fjw9f1je9x3xo","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"checkbox","title":"Utilities","fields":{"value":true},"createAt":1640367568655,"updateAt":1643788318632,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} {"type":"block","data":{"id":"7gwsf4uxtftgjt841zgwydxeere","parentId":"c5xamko6rpibhje3bjreenon7ce","rootId":"bbn1888mprfrm5fjw9f1je9x3xo","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"checkbox","title":"Mobile phone","fields":{"value":true},"createAt":1640367517692,"updateAt":1643788318632,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} {"type":"block","data":{"id":"7j6rbt87htj83bbssod76iumsja","parentId":"c5xamko6rpibhje3bjreenon7ce","rootId":"bbn1888mprfrm5fjw9f1je9x3xo","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"checkbox","title":"Internet","fields":{"value":true},"createAt":1640367560684,"updateAt":1643788318632,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} diff --git a/server/assets/templates-boardarchive/bc41mwxg9ybb69pn9j5zna6d36c/board.jsonl b/server/assets/templates-boardarchive/bc41mwxg9ybb69pn9j5zna6d36c/board.jsonl index 8c1539c76..8f3435f67 100644 --- a/server/assets/templates-boardarchive/bc41mwxg9ybb69pn9j5zna6d36c/board.jsonl +++ b/server/assets/templates-boardarchive/bc41mwxg9ybb69pn9j5zna6d36c/board.jsonl @@ -5,7 +5,7 @@ {"type":"block","data":{"id":"cfk8kwmuhcfd8m8qicz5aqw4mar","parentId":"bc41mwxg9ybb69pn9j5zna6d36c","rootId":"bc41mwxg9ybb69pn9j5zna6d36c","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"card","title":"Project budget approval","fields":{"contentOrder":["a9h4kfaurrprepefrw95i1raoxr","7btyuex8nji8jxn9yieaxgwoe6h","a34hy46bu8bngxcxpz9woui4afa","7ekrgkgq67fdofn9gskpe19bkrc","7ygi1kq3683ya5ydfttuc5rhasr","7qmjyww91rj8a38dsgu5b5wu7hr","7qmmpepfm4byqjqo9m16yp7m3no"],"icon":"💵","isTemplate":false,"properties":{"a8daz81s4xjgke1ww6cwik5w7ye":"16","a972dc7a-5f4c-45d2-8044-8c28c69717f1":"ayz81h9f3dwp7rzzbdebesc7ute","d3d682bf-e074-49d9-8df5-7320921c2d23":"d3bfb50f-f569-4bad-8a3a-dd15c3f60101"}},"createAt":1640281242677,"updateAt":1643788318630,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} {"type":"block","data":{"id":"ckcntrrmcjbywpciau57gw5suoo","parentId":"bc41mwxg9ybb69pn9j5zna6d36c","rootId":"bc41mwxg9ybb69pn9j5zna6d36c","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"card","title":"Conduct market analysis","fields":{"contentOrder":["a6gowxxpgijgip8qzrsp5rmjwqy","771bq4ja3ejfwbgaq78cdpgmjih","asdoj8ffhcirh3x3iys3joeox9o","7k975b49ni7yrfn3nqg7q4x4wde","7e9aj57zouidozb8sf8e1wybywe","71dm4jiu43byubx7pukjiy19pay","719y6x4tkiigd9nwarn1e6ek7ic"],"icon":"📈","isTemplate":false,"properties":{"a8daz81s4xjgke1ww6cwik5w7ye":"40","a972dc7a-5f4c-45d2-8044-8c28c69717f1":"ar6b8m3jxr3asyxhr8iucdbo6yc","d3d682bf-e074-49d9-8df5-7320921c2d23":"87f59784-b859-4c24-8ebe-17c766e081dd"}},"createAt":1640281242851,"updateAt":1643788318630,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} {"type":"block","data":{"id":"vcuoise4b8jn1ffzujfuacymmmr","parentId":"bc41mwxg9ybb69pn9j5zna6d36c","rootId":"bc41mwxg9ybb69pn9j5zna6d36c","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"view","title":"Project Priorities","fields":{"cardOrder":[],"collapsedOptionIds":[],"columnCalculations":{},"columnWidths":{},"defaultTemplateId":"","filter":{"filters":[],"operation":"and"},"groupById":"d3d682bf-e074-49d9-8df5-7320921c2d23","hiddenOptionIds":[],"kanbanCalculations":{"":{"calculation":"sum","propertyId":"a8daz81s4xjgke1ww6cwik5w7ye"},"87f59784-b859-4c24-8ebe-17c766e081dd":{"calculation":"sum","propertyId":"a8daz81s4xjgke1ww6cwik5w7ye"},"98a57627-0f76-471d-850d-91f3ed9fd213":{"calculation":"sum","propertyId":"a8daz81s4xjgke1ww6cwik5w7ye"},"d3bfb50f-f569-4bad-8a3a-dd15c3f60101":{"calculation":"sum","propertyId":"a8daz81s4xjgke1ww6cwik5w7ye"}},"sortOptions":[],"viewType":"board","visibleOptionIds":["d3bfb50f-f569-4bad-8a3a-dd15c3f60101","87f59784-b859-4c24-8ebe-17c766e081dd","98a57627-0f76-471d-850d-91f3ed9fd213",""],"visiblePropertyIds":["a972dc7a-5f4c-45d2-8044-8c28c69717f1","a8daz81s4xjgke1ww6cwik5w7ye"]},"createAt":1640281242551,"updateAt":1643788318630,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} -{"type":"block","data":{"id":"vey61xzc6u38ptnpjqaik6ap91e","parentId":"bc41mwxg9ybb69pn9j5zna6d36c","rootId":"bc41mwxg9ybb69pn9j5zna6d36c","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"view","title":"Progress Tracker","fields":{"cardOrder":["cfk8kwmuhcfd8m8qicz5aqw4mar","cdwqxf4b3utbbxdrgbwtmk9y9eo","c68gyx34srjgjxmrs1z8pj7nbce","ckcntrrmcjbywpciau57gw5suoo","c6w7rxrootfdw7j4fsftc5gsyoo","coxnjt3ro1in19dd1e3awdt338r"],"collapsedOptionIds":[],"columnCalculations":{},"columnWidths":{},"defaultTemplateId":"czw9es1e89fdpjr7cqptr1xq7qh","filter":{"filters":[],"operation":"and"},"groupById":"a972dc7a-5f4c-45d2-8044-8c28c69717f1","hiddenOptionIds":[],"kanbanCalculations":{"":{"calculation":"sum","propertyId":"a8daz81s4xjgke1ww6cwik5w7ye"},"adeo5xuwne3qjue83fcozekz8ko":{"calculation":"sum","propertyId":"a8daz81s4xjgke1ww6cwik5w7ye"},"afi4o5nhnqc3smtzs1hs3ij34dh":{"calculation":"sum","propertyId":"a8daz81s4xjgke1ww6cwik5w7ye"},"ahpyxfnnrzynsw3im1psxpkgtpe":{"calculation":"sum","propertyId":"a8daz81s4xjgke1ww6cwik5w7ye"},"ar6b8m3jxr3asyxhr8iucdbo6yc":{"calculation":"sum","propertyId":"a8daz81s4xjgke1ww6cwik5w7ye"},"ayz81h9f3dwp7rzzbdebesc7ute":{"calculation":"sum","propertyId":"a8daz81s4xjgke1ww6cwik5w7ye"}},"sortOptions":[],"viewType":"board","visibleOptionIds":["ayz81h9f3dwp7rzzbdebesc7ute","ar6b8m3jxr3asyxhr8iucdbo6yc","afi4o5nhnqc3smtzs1hs3ij34dh","adeo5xuwne3qjue83fcozekz8ko","ahpyxfnnrzynsw3im1psxpkgtpe",""],"visiblePropertyIds":["d3d682bf-e074-49d9-8df5-7320921c2d23","a8daz81s4xjgke1ww6cwik5w7ye"]},"createAt":1640281242788,"updateAt":1643788318630,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} +{"type":"block","data":{"id":"vey61xzc6u38ptnpjqaik6ap91e","parentId":"bc41mwxg9ybb69pn9j5zna6d36c","rootId":"bc41mwxg9ybb69pn9j5zna6d36c","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"view","title":"Progress Tracker","fields":{"cardOrder":["cfk8kwmuhcfd8m8qicz5aqw4mar","cdwqxf4b3utbbxdrgbwtmk9y9eo","c68gyx34srjgjxmrs1z8pj7nbce","ckcntrrmcjbywpciau57gw5suoo","c6w7rxrootfdw7j4fsftc5gsyoo","coxnjt3ro1in19dd1e3awdt338r"],"collapsedOptionIds":[],"columnCalculations":{},"columnWidths":{},"defaultTemplateId":"","filter":{"filters":[],"operation":"and"},"groupById":"a972dc7a-5f4c-45d2-8044-8c28c69717f1","hiddenOptionIds":[],"kanbanCalculations":{"":{"calculation":"sum","propertyId":"a8daz81s4xjgke1ww6cwik5w7ye"},"adeo5xuwne3qjue83fcozekz8ko":{"calculation":"sum","propertyId":"a8daz81s4xjgke1ww6cwik5w7ye"},"afi4o5nhnqc3smtzs1hs3ij34dh":{"calculation":"sum","propertyId":"a8daz81s4xjgke1ww6cwik5w7ye"},"ahpyxfnnrzynsw3im1psxpkgtpe":{"calculation":"sum","propertyId":"a8daz81s4xjgke1ww6cwik5w7ye"},"ar6b8m3jxr3asyxhr8iucdbo6yc":{"calculation":"sum","propertyId":"a8daz81s4xjgke1ww6cwik5w7ye"},"ayz81h9f3dwp7rzzbdebesc7ute":{"calculation":"sum","propertyId":"a8daz81s4xjgke1ww6cwik5w7ye"}},"sortOptions":[],"viewType":"board","visibleOptionIds":["ayz81h9f3dwp7rzzbdebesc7ute","ar6b8m3jxr3asyxhr8iucdbo6yc","afi4o5nhnqc3smtzs1hs3ij34dh","adeo5xuwne3qjue83fcozekz8ko","ahpyxfnnrzynsw3im1psxpkgtpe",""],"visiblePropertyIds":["d3d682bf-e074-49d9-8df5-7320921c2d23","a8daz81s4xjgke1ww6cwik5w7ye"]},"createAt":1640281242788,"updateAt":1643788318630,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} {"type":"block","data":{"id":"vfztxwjnegbdh38nfccu3bq1auc","parentId":"bc41mwxg9ybb69pn9j5zna6d36c","rootId":"bc41mwxg9ybb69pn9j5zna6d36c","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"view","title":"Task Overview","fields":{"cardOrder":["c6w7rxrootfdw7j4fsftc5gsyoo","ckcntrrmcjbywpciau57gw5suoo","c68gyx34srjgjxmrs1z8pj7nbce","cfk8kwmuhcfd8m8qicz5aqw4mar","cdwqxf4b3utbbxdrgbwtmk9y9eo","cz8p8gofakfby8kzz83j97db8ph","ce1jm5q5i54enhuu4h3kkay1hcc"],"collapsedOptionIds":[],"columnCalculations":{"a8daz81s4xjgke1ww6cwik5w7ye":"sum"},"columnWidths":{"2a5da320-735c-4093-8787-f56e15cdfeed":196,"__title":280,"a8daz81s4xjgke1ww6cwik5w7ye":139,"a972dc7a-5f4c-45d2-8044-8c28c69717f1":141,"d3d682bf-e074-49d9-8df5-7320921c2d23":110},"defaultTemplateId":"czw9es1e89fdpjr7cqptr1xq7qh","filter":{"filters":[],"operation":"and"},"groupById":"","hiddenOptionIds":[],"kanbanCalculations":{},"sortOptions":[],"viewType":"table","visibleOptionIds":[],"visiblePropertyIds":["a972dc7a-5f4c-45d2-8044-8c28c69717f1","d3d682bf-e074-49d9-8df5-7320921c2d23","2a5da320-735c-4093-8787-f56e15cdfeed","a3zsw7xs8sxy7atj8b6totp3mby","axkhqa4jxr3jcqe4k87g8bhmary","a7gdnz8ff8iyuqmzddjgmgo9ery","a8daz81s4xjgke1ww6cwik5w7ye"]},"createAt":1640281242734,"updateAt":1643788318630,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} {"type":"block","data":{"id":"vi49i1138jpnbiqhyd81beme9zy","parentId":"bc41mwxg9ybb69pn9j5zna6d36c","rootId":"bc41mwxg9ybb69pn9j5zna6d36c","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"view","title":"Task Calendar","fields":{"cardOrder":[],"collapsedOptionIds":[],"columnCalculations":{},"columnWidths":{},"dateDisplayPropertyId":"a3zsw7xs8sxy7atj8b6totp3mby","defaultTemplateId":"","filter":{"filters":[],"operation":"and"},"hiddenOptionIds":[],"kanbanCalculations":{},"sortOptions":[],"viewType":"calendar","visibleOptionIds":[],"visiblePropertyIds":["__title"]},"createAt":1640361708030,"updateAt":1643788318630,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} {"type":"block","data":{"id":"76q9tmzey4byqdpimsdxeg1gx3h","parentId":"c68gyx34srjgjxmrs1z8pj7nbce","rootId":"bc41mwxg9ybb69pn9j5zna6d36c","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"checkbox","title":"[Subtask 1]","fields":{"value":false},"createAt":1641247437494,"updateAt":1643788318632,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} diff --git a/server/assets/templates-boardarchive/bui5izho7dtn77xg3thkiqprc9r/board.jsonl b/server/assets/templates-boardarchive/bui5izho7dtn77xg3thkiqprc9r/board.jsonl index 694c799d4..0197ba237 100644 --- a/server/assets/templates-boardarchive/bui5izho7dtn77xg3thkiqprc9r/board.jsonl +++ b/server/assets/templates-boardarchive/bui5izho7dtn77xg3thkiqprc9r/board.jsonl @@ -6,7 +6,7 @@ {"type":"block","data":{"id":"cp1m1wrpfatdxikhwkf58oo5k3o","parentId":"bui5izho7dtn77xg3thkiqprc9r","rootId":"bui5izho7dtn77xg3thkiqprc9r","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"card","title":"Review API design","fields":{"contentOrder":["ahsamufik97nsfxjgx9cs6cmzme"],"icon":"🛣️","isTemplate":false,"properties":{"20717ad3-5741-4416-83f1-6f133fff3d11":"424ea5e3-9aa1-4075-8c5c-01b44b66e634","50117d52-bcc7-4750-82aa-831a351c44a0":"8c557f69-b0ed-46ec-83a3-8efab9d47ef5","60985f46-3e41-486e-8213-2b987440ea1c":"14892380-1a32-42dd-8034-a0cea32bc7e6","ai7ajsdk14w7x5s8up3dwir77te":"https://mattermost.com/boards/","f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e":"c62172ea-5da7-4dec-8186-37267d8ee9a7"}},"createAt":1640363550754,"updateAt":1643788318631,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} {"type":"block","data":{"id":"cqfy6g434pigk3p7j3gq55trq9o","parentId":"bui5izho7dtn77xg3thkiqprc9r","rootId":"bui5izho7dtn77xg3thkiqprc9r","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"card","title":"Icons don't display","fields":{"contentOrder":["axfkn6tuy4igubj3ka99tbymb8o","acbpep9wxdtyg8gg3fi6h1hgoro","7tedfdyq4p7g77dmkrebryh4jor"],"icon":"💻","isTemplate":false,"properties":{"20717ad3-5741-4416-83f1-6f133fff3d11":"1fdbb515-edd2-4af5-80fc-437ed2211a49","50117d52-bcc7-4750-82aa-831a351c44a0":"8c557f69-b0ed-46ec-83a3-8efab9d47ef5","60985f46-3e41-486e-8213-2b987440ea1c":"ed4a5340-460d-461b-8838-2c56e8ee59fe","ai7ajsdk14w7x5s8up3dwir77te":"https://mattermost.com/boards/","f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e":"e6a7f297-4440-4783-8ab3-3af5ba62ca11"}},"createAt":1640363550868,"updateAt":1643788318631,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} {"type":"block","data":{"id":"v1uubwdzrw7fsxnd6pss1dyhh5e","parentId":"bui5izho7dtn77xg3thkiqprc9r","rootId":"bui5izho7dtn77xg3thkiqprc9r","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"view","title":"Calendar View","fields":{"cardOrder":[],"collapsedOptionIds":[],"columnCalculations":{},"columnWidths":{},"dateDisplayPropertyId":"a4378omyhmgj3bex13sj4wbpfiy","defaultTemplateId":"","filter":{"filters":[],"operation":"and"},"hiddenOptionIds":[],"kanbanCalculations":{},"sortOptions":[],"viewType":"calendar","visibleOptionIds":[],"visiblePropertyIds":["__title"]},"createAt":1640379248049,"updateAt":1643788318631,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} -{"type":"block","data":{"id":"v7n4sc9cre7gsbq9yydsuekpg8a","parentId":"bui5izho7dtn77xg3thkiqprc9r","rootId":"bui5izho7dtn77xg3thkiqprc9r","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"view","title":"Board: Sprints","fields":{"cardOrder":["c3jawn6e4fbr3jctthy9xxkdsqe","c5trb4319wi8n3x4r4f7f83ytdc","c9p4bdasriifc7qgihzhjm63ugy","cqfy6g434pigk3p7j3gq55trq9o","chfrdo1nb3p8ofnbftyinr6949o","cp1m1wrpfatdxikhwkf58oo5k3o"],"collapsedOptionIds":[],"columnCalculations":{},"columnWidths":{},"defaultTemplateId":"cidz4imnqhir48brz6e8hxhfrhy","filter":{"filters":[],"operation":"and"},"groupById":"60985f46-3e41-486e-8213-2b987440ea1c","hiddenOptionIds":[],"kanbanCalculations":{},"sortOptions":[{"propertyId":"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e","reversed":false}],"viewType":"board","visibleOptionIds":["c01676ca-babf-4534-8be5-cce2287daa6c","ed4a5340-460d-461b-8838-2c56e8ee59fe","14892380-1a32-42dd-8034-a0cea32bc7e6",""],"visiblePropertyIds":["20717ad3-5741-4416-83f1-6f133fff3d11","f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e"]},"createAt":1640363550811,"updateAt":1643788318631,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} +{"type":"block","data":{"id":"v7n4sc9cre7gsbq9yydsuekpg8a","parentId":"bui5izho7dtn77xg3thkiqprc9r","rootId":"bui5izho7dtn77xg3thkiqprc9r","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"view","title":"Board: Sprints","fields":{"cardOrder":["c3jawn6e4fbr3jctthy9xxkdsqe","c5trb4319wi8n3x4r4f7f83ytdc","c9p4bdasriifc7qgihzhjm63ugy","cqfy6g434pigk3p7j3gq55trq9o","chfrdo1nb3p8ofnbftyinr6949o","cp1m1wrpfatdxikhwkf58oo5k3o"],"collapsedOptionIds":[],"columnCalculations":{},"columnWidths":{},"defaultTemplateId":"","filter":{"filters":[],"operation":"and"},"groupById":"60985f46-3e41-486e-8213-2b987440ea1c","hiddenOptionIds":[],"kanbanCalculations":{},"sortOptions":[{"propertyId":"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e","reversed":false}],"viewType":"board","visibleOptionIds":["c01676ca-babf-4534-8be5-cce2287daa6c","ed4a5340-460d-461b-8838-2c56e8ee59fe","14892380-1a32-42dd-8034-a0cea32bc7e6",""],"visiblePropertyIds":["20717ad3-5741-4416-83f1-6f133fff3d11","f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e"]},"createAt":1640363550811,"updateAt":1643788318631,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} {"type":"block","data":{"id":"v8sa3mo81d38rbmd8bz4n6dg7qc","parentId":"bui5izho7dtn77xg3thkiqprc9r","rootId":"bui5izho7dtn77xg3thkiqprc9r","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"view","title":"List: Tasks 🔨","fields":{"cardOrder":[],"collapsedOptionIds":[],"columnCalculations":{},"columnWidths":{"50117d52-bcc7-4750-82aa-831a351c44a0":139,"__title":280},"defaultTemplateId":"","filter":{"filters":[{"condition":"includes","propertyId":"20717ad3-5741-4416-83f1-6f133fff3d11","values":["6eea96c9-4c61-4968-8554-4b7537e8f748"]}],"operation":"and"},"hiddenOptionIds":[],"kanbanCalculations":{},"sortOptions":[{"propertyId":"50117d52-bcc7-4750-82aa-831a351c44a0","reversed":true}],"viewType":"table","visibleOptionIds":[],"visiblePropertyIds":["50117d52-bcc7-4750-82aa-831a351c44a0","20717ad3-5741-4416-83f1-6f133fff3d11","60985f46-3e41-486e-8213-2b987440ea1c","f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e"]},"createAt":1640363550980,"updateAt":1643788318631,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} {"type":"block","data":{"id":"vi43bqxsho3fmjbu1oa8qafwo4c","parentId":"bui5izho7dtn77xg3thkiqprc9r","rootId":"bui5izho7dtn77xg3thkiqprc9r","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"view","title":"Board: Status","fields":{"cardOrder":["c3jawn6e4fbr3jctthy9xxkdsqe","cm4w7cc3aac6s9jdcujbs4j8f4r","c6egh6cpnj137ixdoitsoxq17oo","cct9u78utsdyotmejbmwwg66ihr","cmft87it1q7yebbd51ij9k65xbw","c9fe77j9qcruxf4itzib7ag6f1c","coup7afjknqnzbdwghiwbsq541w","c5ex1hndz8qyc8gx6ofbfeksftc"],"collapsedOptionIds":[],"columnCalculations":{},"columnWidths":{},"defaultTemplateId":"cidz4imnqhir48brz6e8hxhfrhy","filter":{"filters":[],"operation":"and"},"groupById":"50117d52-bcc7-4750-82aa-831a351c44a0","hiddenOptionIds":[],"kanbanCalculations":{},"sortOptions":[{"propertyId":"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e","reversed":false}],"viewType":"board","visibleOptionIds":["8c557f69-b0ed-46ec-83a3-8efab9d47ef5","ec6d2bc5-df2b-4f77-8479-e59ceb039946","849766ba-56a5-48d1-886f-21672f415395",""],"visiblePropertyIds":["20717ad3-5741-4416-83f1-6f133fff3d11","60985f46-3e41-486e-8213-2b987440ea1c","f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e"]},"createAt":1640363551099,"updateAt":1643788318631,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} {"type":"block","data":{"id":"vod5de87tz7nxpji31oou4ine3c","parentId":"bui5izho7dtn77xg3thkiqprc9r","rootId":"bui5izho7dtn77xg3thkiqprc9r","createdBy":"edrkkih4cinzf8ueeszh6rmfoo","modifiedBy":"edrkkih4cinzf8ueeszh6rmfoo","schema":1,"type":"view","title":"List: Bugs 🐞","fields":{"cardOrder":[],"collapsedOptionIds":[],"columnCalculations":{},"columnWidths":{"50117d52-bcc7-4750-82aa-831a351c44a0":145,"__title":280},"defaultTemplateId":"","filter":{"filters":[{"condition":"includes","propertyId":"20717ad3-5741-4416-83f1-6f133fff3d11","values":["1fdbb515-edd2-4af5-80fc-437ed2211a49"]}],"operation":"and"},"hiddenOptionIds":[],"kanbanCalculations":{},"sortOptions":[{"propertyId":"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e","reversed":false}],"viewType":"table","visibleOptionIds":[],"visiblePropertyIds":["50117d52-bcc7-4750-82aa-831a351c44a0","20717ad3-5741-4416-83f1-6f133fff3d11","60985f46-3e41-486e-8213-2b987440ea1c","f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e"]},"createAt":1640363550690,"updateAt":1643788318631,"deleteAt":0,"workspaceId":"855b3j34ojn5p8f36yhu8336fe"}} diff --git a/server/client/client.go b/server/client/client.go index f7f1a45f5..e082a3c5f 100644 --- a/server/client/client.go +++ b/server/client/client.go @@ -206,6 +206,36 @@ func (c *Client) GetTeam(teamID string) (*model.Team, *Response) { return model.TeamFromJSON(r.Body), BuildResponse(r) } +func (c *Client) GetTeamBoardsInsights(teamID string, userID string, timeRange string, page int, perPage int) (*model.BoardInsightsList, *Response) { + query := fmt.Sprintf("?time_range=%v&page=%v&per_page=%v", timeRange, page, perPage) + r, err := c.DoAPIGet(c.GetTeamRoute(teamID)+"/boards/insights"+query, "") + if err != nil { + return nil, BuildErrorResponse(r, err) + } + defer closeBody(r) + + var boardInsightsList *model.BoardInsightsList + if jsonErr := json.NewDecoder(r.Body).Decode(&boardInsightsList); jsonErr != nil { + return nil, BuildErrorResponse(r, jsonErr) + } + return boardInsightsList, BuildResponse(r) +} + +func (c *Client) GetUserBoardsInsights(teamID string, userID string, timeRange string, page int, perPage int) (*model.BoardInsightsList, *Response) { + query := fmt.Sprintf("?time_range=%v&page=%v&per_page=%v&team_id=%v", timeRange, page, perPage, teamID) + r, err := c.DoAPIGet(c.GetMeRoute()+"/boards/insights"+query, "") + if err != nil { + return nil, BuildErrorResponse(r, err) + } + defer closeBody(r) + + var boardInsightsList *model.BoardInsightsList + if jsonErr := json.NewDecoder(r.Body).Decode(&boardInsightsList); jsonErr != nil { + return nil, BuildErrorResponse(r, jsonErr) + } + return boardInsightsList, BuildResponse(r) +} + func (c *Client) GetBlocksForBoard(boardID string) ([]model.Block, *Response) { r, err := c.DoAPIGet(c.GetBlocksRoute(boardID), "") if err != nil { @@ -318,6 +348,38 @@ func (c *Client) CreateBoardsAndBlocks(bab *model.BoardsAndBlocks) (*model.Board return model.BoardsAndBlocksFromJSON(r.Body), BuildResponse(r) } +func (c *Client) CreateCategory(category model.Category) (*model.Category, *Response) { + r, err := c.DoAPIPost(c.GetTeamRoute(category.TeamID)+"/categories", toJSON(category)) + if err != nil { + return nil, BuildErrorResponse(r, err) + } + defer closeBody(r) + + return model.CategoryFromJSON(r.Body), BuildResponse(r) +} + +func (c *Client) UpdateCategoryBoard(teamID, categoryID, boardID string) *Response { + r, err := c.DoAPIPost(fmt.Sprintf("%s/categories/%s/boards/%s", c.GetTeamRoute(teamID), categoryID, boardID), "") + if err != nil { + return BuildErrorResponse(r, err) + } + defer closeBody(r) + + return BuildResponse(r) +} + +func (c *Client) GetUserCategoryBoards(teamID string) ([]model.CategoryBoards, *Response) { + r, err := c.DoAPIGet(c.GetTeamRoute(teamID)+"/categories", "") + if err != nil { + return nil, BuildErrorResponse(r, err) + } + defer closeBody(r) + + var categoryBoards []model.CategoryBoards + _ = json.NewDecoder(r.Body).Decode(&categoryBoards) + return categoryBoards, BuildResponse(r) +} + func (c *Client) PatchBoardsAndBlocks(pbab *model.PatchBoardsAndBlocks) (*model.BoardsAndBlocks, *Response) { r, err := c.DoAPIPatch(c.GetBoardsAndBlocksRoute(), toJSON(pbab)) if err != nil { @@ -369,7 +431,7 @@ func (c *Client) GetRegisterRoute() string { return "/register" } -func (c *Client) Register(request *api.RegisterRequest) (bool, *Response) { +func (c *Client) Register(request *model.RegisterRequest) (bool, *Response) { r, err := c.DoAPIPost(c.GetRegisterRoute(), toJSON(&request)) if err != nil { return false, BuildErrorResponse(r, err) @@ -383,14 +445,14 @@ func (c *Client) GetLoginRoute() string { return "/login" } -func (c *Client) Login(request *api.LoginRequest) (*api.LoginResponse, *Response) { +func (c *Client) Login(request *model.LoginRequest) (*model.LoginResponse, *Response) { r, err := c.DoAPIPost(c.GetLoginRoute(), toJSON(&request)) if err != nil { return nil, BuildErrorResponse(r, err) } defer closeBody(r) - data, err := api.LoginResponseFromJSON(r.Body) + data, err := model.LoginResponseFromJSON(r.Body) if err != nil { return nil, BuildErrorResponse(r, err) } @@ -450,7 +512,7 @@ func (c *Client) GetUserChangePasswordRoute(id string) string { return fmt.Sprintf("/users/%s/changepassword", id) } -func (c *Client) UserChangePassword(id string, data *api.ChangePasswordRequest) (bool, *Response) { +func (c *Client) UserChangePassword(id string, data *model.ChangePasswordRequest) (bool, *Response) { r, err := c.DoAPIPost(c.GetUserChangePasswordRoute(id), toJSON(&data)) if err != nil { return false, BuildErrorResponse(r, err) diff --git a/server/go.mod b/server/go.mod index 6fc86d446..a74a47b08 100644 --- a/server/go.mod +++ b/server/go.mod @@ -4,23 +4,23 @@ go 1.18 require ( github.com/Masterminds/squirrel v1.5.2 - github.com/go-sql-driver/mysql v1.6.0 github.com/golang/mock v1.6.0 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/krolaw/zipstream v0.0.0-20180621105154-0a2661891f94 github.com/lib/pq v1.10.6 - github.com/mattermost/mattermost-plugin-api v0.0.28-0.20220623051512-0afd85e854d4 - github.com/mattermost/mattermost-server/v6 v6.0.0-20220711175838-7ee7523729e6 + github.com/mattermost/mattermost-plugin-api v0.0.29-0.20220801143717-73008cfda2fb + github.com/mattermost/mattermost-server/v6 v6.0.0-20220802151854-f07c31c5d933 github.com/mattermost/morph v0.0.0-20220401091636-39f834798da8 github.com/mattn/go-sqlite3 v2.0.3+incompatible + github.com/mgdelacroix/foundation v0.0.0-20220812143423-0bfc18f73538 github.com/oklog/run v1.1.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.12.1 github.com/rudderlabs/analytics-go v3.3.2+incompatible github.com/sergi/go-diff v1.2.0 github.com/spf13/viper v1.10.1 - github.com/stretchr/testify v1.7.2 + github.com/stretchr/testify v1.8.0 github.com/wiggin77/merror v1.0.3 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e ) @@ -28,14 +28,17 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect + github.com/blang/semver/v4 v4.0.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.0 // indirect - github.com/dyatlov/go-opengraph v0.0.0-20210112100619-dae8665a5b09 // indirect + github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a // indirect github.com/fatih/color v1.13.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect + github.com/go-sql-driver/mysql v1.6.0 // indirect + github.com/golang-migrate/migrate/v4 v4.15.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/uuid v1.3.0 // indirect github.com/graph-gophers/graphql-go v1.4.0 // indirect @@ -43,6 +46,7 @@ require ( github.com/hashicorp/go-plugin v1.4.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect + github.com/jmoiron/sqlx v1.3.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/klauspost/compress v1.15.6 // indirect @@ -53,6 +57,7 @@ require ( github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d // indirect github.com/mattermost/logr/v2 v2.0.15 // indirect + github.com/mattermost/squirrel v0.2.0 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect @@ -92,6 +97,7 @@ require ( github.com/yuin/goldmark v1.4.12 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/net v0.0.0-20220614195744-fb05da6f9022 // indirect + golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/tools v0.1.11 // indirect diff --git a/server/go.sum b/server/go.sum index 94fa5c157..7ba195d4d 100644 --- a/server/go.sum +++ b/server/go.sum @@ -53,7 +53,6 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl 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= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -code.sajari.com/docconv v1.2.0/go.mod h1:r8yfCP6OKbZ9Xkd87aBa4nfpk6ud/PoyLwex3n6cXSc= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= @@ -62,7 +61,6 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= -github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck= @@ -83,17 +81,10 @@ github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935 github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 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/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= -github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= -github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= -github.com/HdrHistogram/hdrhistogram-go v0.9.0/go.mod h1:nxrse8/Tzg2tg3DZcZjm6qEclQKK70g0KxO61gFFZD4= -github.com/JalfResi/justext v0.0.0-20170829062021-c0282dea7198/go.mod h1:0SURuH1rsE8aVWvutuMZghRNrNrYEUzibzJfhEYR8L0= -github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/squirrel v1.5.2 h1:UiOEi2ZX4RCSkpiNDQN5kro/XIBpSRk9iTqdIRPzUXE= github.com/Masterminds/squirrel v1.5.2/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= @@ -122,20 +113,11 @@ github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:m github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/goquery v1.4.1/go.mod h1:T9ezsOHcCrDCgA8aF1Cqr3sSYbO/xgdy8/R/XiIMAhA= -github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= -github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/RoaringBitmap/roaring v0.9.4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA= -github.com/RoaringBitmap/roaring v1.2.1/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA= -github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= -github.com/advancedlogic/GoOse v0.0.0-20191112112754-e742535969c1/go.mod h1:f3HCSN1fBWjcpGtXyM119MJgeQl838v6so/PQOqvE1w= -github.com/advancedlogic/GoOse v0.0.0-20210820140952-9d5822d4a625/go.mod h1:f3HCSN1fBWjcpGtXyM119MJgeQl838v6so/PQOqvE1w= -github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 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= @@ -144,28 +126,17 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= -github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= -github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= -github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= -github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY= -github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow/go/arrow v0.0.0-20210818145353-234c94e4ce64/go.mod h1:2qMFB56yOP3KzkB3PbYZ4AlUFg3a88F67TIx5lB/WwY= github.com/apache/arrow/go/arrow v0.0.0-20211013220434-5962184e7a30/go.mod h1:Q7yQnSMnLvcXlZ8RV+jwz/6y1rQTqbX6C82SndT52Zs= -github.com/araddon/dateparse v0.0.0-20180729174819-cfd92a431d0e/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI= -github.com/araddon/dateparse v0.0.0-20200409225146-d820a6159ab1/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI= -github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/avct/uasurfer v0.0.0-20191028135549-26b5daa857f1/go.mod h1:noBAuukeYOXa0aXGqxr24tADqkwDO2KRD15FsuaZ5a8= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.44.34/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go-v2 v1.8.0/go.mod h1:xEFuWz+3TYdlPRuo+CqATbeDWIWyaT5uAPwPaWtgse0= github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= github.com/aws/aws-sdk-go-v2/config v1.6.0/go.mod h1:TNtBVmka80lRPk5+S9ZqVfFszOQAGJJ9KbT3EM3CHNU= @@ -192,8 +163,6 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.6.1/go.mod h1:hLZ/AnkIKHLuPGjEiyghNE github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g= github.com/aws/smithy-go v1.7.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= -github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -204,41 +173,13 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/bits-and-blooms/bitset v1.2.2/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/blevesearch/bleve/v2 v2.3.2/go.mod h1:96+xE5pZUOsr3Y4vHzV1cBC837xZCpwLlX0hrrxnvIg= -github.com/blevesearch/bleve_index_api v1.0.1/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4= -github.com/blevesearch/bleve_index_api v1.0.2/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4= -github.com/blevesearch/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:9eJDeqxJ3E7WnLebQUlPD7ZjSce7AnDb9vjGmMCbD0A= -github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M= -github.com/blevesearch/goleveldb v1.0.1/go.mod h1:WrU8ltZbIp0wAoig/MHbrPCXSOLpe79nz5lv5nqfYrQ= -github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk= -github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+7LMvAB5IbSA= -github.com/blevesearch/mmap-go v1.0.3/go.mod h1:pYvKl/grLQrBxuaRYgoTssa4rVujYYeenDp++2E+yvs= -github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs= -github.com/blevesearch/scorch_segment_api/v2 v2.1.0/go.mod h1:uch7xyyO/Alxkuxa+CGs79vw0QY8BENSBjg6Mw5L5DE= -github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ= -github.com/blevesearch/snowball v0.6.1/go.mod h1:ZF0IBg5vgpeoUhnMza2v0A/z8m1cWPlwhke08LpNusg= -github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs= -github.com/blevesearch/upsidedown_store_api v1.0.1/go.mod h1:MQDVGpHZrpe3Uy26zJBf/a8h0FZY6xJbthIMm8myH2Q= -github.com/blevesearch/vellum v1.0.7/go.mod h1:doBZpmRhwTsASB4QdUZANlJvqVAUdUyX0ZK7QJCTeBE= -github.com/blevesearch/vellum v1.0.8/go.mod h1:+cpRi/tqq49xUYSQN2P7A5zNSNrS+MscLeeaZ3J46UA= -github.com/blevesearch/zapx/v11 v11.3.3/go.mod h1:YzTfUm4kS3e8OmTXDHVV8OzC5MWPO/VPJZQgPNVb4Lc= -github.com/blevesearch/zapx/v11 v11.3.4/go.mod h1:HJ7qdfBxdziuymKvXbsBVhCK5pB98tdzQbc8pJV6tJo= -github.com/blevesearch/zapx/v12 v12.3.3/go.mod h1:RMl6lOZqF+sTxKvhQDJ5yK2LT3Mu7E2p/jGdjAaiRxs= -github.com/blevesearch/zapx/v12 v12.3.4/go.mod h1:uQrKrK9XjXAAsJfAIE8ViLqIKP/keA2DQhS1XXpjkwA= -github.com/blevesearch/zapx/v13 v13.3.3/go.mod h1:eppobNM35U4C22yDvTuxV9xPqo10pwfP/jugL4INWG4= -github.com/blevesearch/zapx/v13 v13.3.4/go.mod h1:Wl7hO1gT+IDvJb7i06g2iW5Qvw0KzncJPsBx7WGWhLA= -github.com/blevesearch/zapx/v14 v14.3.3/go.mod h1:zXNcVzukh0AvG57oUtT1T0ndi09H0kELNaNmekEy0jw= -github.com/blevesearch/zapx/v14 v14.3.4/go.mod h1:b1YhRXXhAj9i+9aOwhRKCHUmJyYieK/QbDvPJDLddUk= -github.com/blevesearch/zapx/v15 v15.3.3/go.mod h1:C+f/97ZzTzK6vt/7sVlZdzZxKu+5+j4SrGCvr9dJzaY= -github.com/blevesearch/zapx/v15 v15.3.4/go.mod h1:TQ/qDC2q7TSSpeC6Vgr9fDN56Ra0u49lZJQ4v30WEx4= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= @@ -290,7 +231,6 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= -github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= @@ -390,7 +330,6 @@ github.com/containers/ocicrypt v1.1.2/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= @@ -407,12 +346,8 @@ github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k= -github.com/couchbase/moss v0.2.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -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/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -429,15 +364,10 @@ 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/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= -github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/dgoogauth v0.0.0-20190221195224-5a805980a5f3/go.mod h1:hEfFauPHz7+NnjR/yHJGhrKo1Za+zStgwUETx3yzqgY= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dhui/dktest v0.3.10/go.mod h1:h5Enh0nG3Qbo9WjNFRrwmKUaePEBhXMOygbz3Ww7Sz0= -github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= @@ -456,15 +386,12 @@ github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= -github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dyatlov/go-opengraph v0.0.0-20210112100619-dae8665a5b09 h1:AQLr//nh20BzN3hIWj2+/Gt3FwSs8Nwo/nz4hMIcLPg= -github.com/dyatlov/go-opengraph v0.0.0-20210112100619-dae8665a5b09/go.mod h1:nYia/MIs9OyvXXYboPmNOj0gVWo97Wx0sde+ZuKkoM4= +github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a h1:etIrTD8BQqzColk9nKRusM9um5+1q0iOEJLqfBMIK64= +github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a/go.mod h1:emQhSYTXqB0xxjLITTw4EaWZ+8IIQYw+kx9GqNUKdLg= github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= @@ -480,17 +407,13 @@ github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPO github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= -github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= @@ -509,19 +432,13 @@ github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXt github.com/gabriel-vasile/mimetype v1.3.1/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= -github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= -github.com/getsentry/sentry-go v0.13.0/go.mod h1:EOsfu5ZdvKPfeHYV6pTVQnsjfp30+XA7//UooKNumH0= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gigawattio/window v0.0.0-20180317192513-0f5467e35573/go.mod h1:eBvb3i++NHDH4Ugo9qCvMw8t0mTSctaEa5blJbWcNxs= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-asn1-ber/asn1-ber v1.3.2-0.20191121212151-29be175fc3a3/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= @@ -549,7 +466,6 @@ github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -564,15 +480,6 @@ github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dp github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-redis/redis/v8 v8.0.0/go.mod h1:isLoQT/NFSP7V67lyvM9GmdvLdyZ7pEhsXvvyQtnQTo= -github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= -github.com/go-resty/resty/v2 v2.0.0/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8= -github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU= -github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= @@ -604,9 +511,6 @@ github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWe github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= @@ -624,9 +528,9 @@ github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5 github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-migrate/migrate/v4 v4.15.2 h1:vU+M05vs6jWHKDdmE1Ecwj0BznygFc4QsdRe2E/L7kc= github.com/golang-migrate/migrate/v4 v4.15.2/go.mod h1:f2toGLkYqD3JH+Todi4aZ2ZdbeUNx4sIwiOK96rE9Lw= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= @@ -668,13 +572,9 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= -github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= 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/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= @@ -717,7 +617,6 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/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-20210407192527-94a9f03dee38/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= @@ -739,24 +638,18 @@ github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2c github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= -github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/graph-gophers/dataloader/v6 v6.0.0/go.mod h1:J15OZSnOoZgMkijpbZcwCmglIDYqlUiTEE1xLPbyqZM= github.com/graph-gophers/graphql-go v1.4.0 h1:JE9wveRTSXwJyjdRd6bOQ7Ob5bewTUQ58Jv4OiVdpdE= github.com/graph-gophers/graphql-go v1.4.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= @@ -768,21 +661,21 @@ github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpg github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c/go.mod h1:ObS/W+h8RYb1Y7fYivughjxojTmIu5iAIjSrSLCLeqE= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.2.1 h1:YQsLlGDJgwhXFpucSPyVbCBviQtjlHv3jLTlp8YmtEw= github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ= github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= @@ -791,18 +684,15 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX 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/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 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 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 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.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I= github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -814,14 +704,8 @@ github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= -github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= -github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= -github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= -github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= -github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= @@ -867,12 +751,8 @@ github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0f github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= -github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= -github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= -github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -882,6 +762,7 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= @@ -891,7 +772,6 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -908,39 +788,24 @@ github.com/k0kubun/pp v2.3.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3t github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= -github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= -github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= -github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= -github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= -github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 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.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY= github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.13 h1:1XxvOiqXZ8SULZUKim/wncr3wZ38H4yCuVDvKdK9OGs= github.com/klauspost/cpuid/v2 v2.0.13/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 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/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -950,7 +815,6 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN 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.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= @@ -961,15 +825,10 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/krolaw/zipstream v0.0.0-20180621105154-0a2661891f94 h1:+AIlO01SKT9sfWU5CLWi0cfHc7dQwgGz3FhFRzXLoMg= github.com/krolaw/zipstream v0.0.0-20180621105154-0a2661891f94/go.mod h1:TcE3PIIkVWbP/HjhRAafgCjRKvDOi086iqp9VkNX/ng= github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= -github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= -github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/levigross/exp-html v0.0.0-20120902181939-8df60c69a8f5/go.mod h1:QMe2wuKJ0o7zIVE8AqiT8rd8epmm6WDIZ2wyuBqYPzM= 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= @@ -983,7 +842,6 @@ github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -998,28 +856,24 @@ github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kN github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 h1:Khvh6waxG1cHc4Cz5ef9n3XVCxRWpAKUtqg9PJl5+y8= github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404/go.mod h1:RyS7FDNQlzF1PsjbJWHRI35exqaKGSO9qD4iv8QjE34= -github.com/mattermost/gziphandler v0.0.1/go.mod h1:CvvZR7sXqhj81V2swXuQY7T04Ccc89u7W7pHNPKev8g= github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d h1:/RJ/UV7M5c7L2TQ0KNm4yZxxFvC1nvRz/gY/Daa35aI= github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d/go.mod h1:HLbgMEI5K131jpxGazJ97AxfPDt31osq36YS1oxFQPQ= github.com/mattermost/logr/v2 v2.0.15 h1:+WNbGcsc3dBao65eXlceB6dTILNJRIrvubnsTl3zBew= github.com/mattermost/logr/v2 v2.0.15/go.mod h1:mpPp935r5dIkFDo2y9Q87cQWhFR/4xXpNh0k/y8Hmwg= -github.com/mattermost/mattermost-plugin-api v0.0.28-0.20220623051512-0afd85e854d4 h1:TF1yBBsLntuNb3wc3DRg30S9i6tv1JwtREtXd7Gnv9E= -github.com/mattermost/mattermost-plugin-api v0.0.28-0.20220623051512-0afd85e854d4/go.mod h1:jtiaM6selJi1Od1zGZDGO78hZyG0gI4/I2/8mza4OZg= -github.com/mattermost/mattermost-server/v6 v6.0.0-20220622145221-00016e3a4ff4/go.mod h1:e2CtTtnty6oH8CiHm40cMOqJ+dJeWEK39/tobCkeMAk= -github.com/mattermost/mattermost-server/v6 v6.0.0-20220711175838-7ee7523729e6 h1:lfkO5s/ZwuD2esAHGX+0EtmcsAVXJ0S5Xn37uWW/WoQ= -github.com/mattermost/mattermost-server/v6 v6.0.0-20220711175838-7ee7523729e6/go.mod h1:e2CtTtnty6oH8CiHm40cMOqJ+dJeWEK39/tobCkeMAk= +github.com/mattermost/mattermost-plugin-api v0.0.29-0.20220801143717-73008cfda2fb h1:q1qXKVv59rA2gcQ7lVLc5OlWBmfsR3i8mdGD5EZesyk= +github.com/mattermost/mattermost-plugin-api v0.0.29-0.20220801143717-73008cfda2fb/go.mod h1:PIeo40t9VTA4Wu1FwjzH7QmcgC3SRyk/ohCwJw4/oSo= +github.com/mattermost/mattermost-server/v6 v6.0.0-20220802151854-f07c31c5d933 h1:h7EibO8cwWeK8dLhC/A5tKGbkYSuJKZ0+2EXW7jDHoA= +github.com/mattermost/mattermost-server/v6 v6.0.0-20220802151854-f07c31c5d933/go.mod h1:otnBnKY9Y0eNkUKeD161de+BUBlESwANTnrkPT/392Y= github.com/mattermost/morph v0.0.0-20220401091636-39f834798da8 h1:gwliVjCTqAC01mSCNqa5nJ/4MmGq50vrjsottIhQ4d8= github.com/mattermost/morph v0.0.0-20220401091636-39f834798da8/go.mod h1:jxM3g1bx+k2Thz7jofcHguBS8TZn5Pc+o5MGmORObhw= -github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0/go.mod h1:nV5bfVpT//+B1RPD2JvRnxbkLmJEYXmRaaVl15fsXjs= +github.com/mattermost/squirrel v0.2.0 h1:8ZWeyf+MWQ2cL7hu9REZgLtz2IJi51qqZEovI3T3TT8= github.com/mattermost/squirrel v0.2.0/go.mod h1:NPPtk+CdpWre4GxMGoOpzEVFVc0ZoEFyJBZGCtn9nSU= 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.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -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.8/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.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= @@ -1029,16 +883,10 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd 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.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= @@ -1048,24 +896,19 @@ github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= -github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= -github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= +github.com/mgdelacroix/foundation v0.0.0-20220812143423-0bfc18f73538 h1:6mFhRD89wtsxh7g8V6og9DR4y/UGlzzehc1c3O9tbMQ= +github.com/mgdelacroix/foundation v0.0.0-20220812143423-0bfc18f73538/go.mod h1:ZwobEfNHde7sU2pGybCWEnSlQ2r+MGrHGOKLphHZ42g= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= -github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= -github.com/microcosm-cc/bluemonday v1.0.18/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.0.28 h1:VMr3K5qGIEt+/KW3poopRh8mzi5RwuCjmrmstK196Fg= github.com/minio/minio-go/v7 v7.0.28/go.mod h1:x81+AX5gHSfCSqw7jxRKHvxUXMlE5uKX0Vb75Xk5yYg= -github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= @@ -1073,7 +916,6 @@ github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceT github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= @@ -1082,7 +924,6 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= @@ -1106,9 +947,7 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= -github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mutecomm/go-sqlcipher/v4 v4.4.0/go.mod h1:PyN04SaWalavxRGH9E8ZftG6Ju7rsPrGmQRjrEaVpiY= @@ -1116,33 +955,20 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1:ncO5VaFWh0Nrt+4KT4mOZboaczBZcLuHrG+/sUeP8gI= -github.com/nicksnyder/go-i18n/v2 v2.0.3/go.mod h1:oDab7q8XCYMRlcrBnaY/7B1eOectbvj6B1UPBT+p5jo= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= -github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.0-20180506121414-d4647c9c7a84/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -1151,24 +977,16 @@ github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= -github.com/oov/psd v0.0.0-20220121172623-5db5eafcecbb/go.mod h1:GHI1bnmAcbp96z6LNfBJvtrjxhaXGkbsk967utPlvL8= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -1199,14 +1017,7 @@ github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuh github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= -github.com/otiai10/gosseract/v2 v2.2.4/go.mod h1:ahOp/kHojnOMGv1RaUnR0jwY5JVa6BYKhYAS8nbMLSo= -github.com/otiai10/gosseract/v2 v2.3.1/go.mod h1:2ZOGgdTIXQzCS5f+N1HkcXRgDX6K3ZoYe3Yvo++cpp4= -github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= @@ -1221,13 +1032,9 @@ github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.8/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= -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-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -1282,25 +1089,15 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/reflog/dateconstraints v0.2.1/go.mod h1:Ax8AxTBcJc3E/oVS2hd2j7RDM/5MDtuPwuR7lIHtPLo= github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/richardlehane/mscfb v1.0.3/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= -github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= -github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= -github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 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.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -1310,18 +1107,13 @@ github.com/rudderlabs/analytics-go v3.3.2+incompatible h1:bDajEJTYhfHjNYxbQFMA/2 github.com/rudderlabs/analytics-go v3.3.2+incompatible/go.mod h1:LF8/ty9kUX4PTY3l5c97K3nZZaX5Hwsvt+NBaRL/f30= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= -github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= -github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= @@ -1337,7 +1129,6 @@ github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIl github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= @@ -1347,7 +1138,6 @@ github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9A github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= -github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= @@ -1357,9 +1147,7 @@ github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1l github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= -github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= -github.com/simplereach/timeutils v1.2.0/go.mod h1:VVbQDfN/FHRZa1LSqcwo4kNZ62OOyqLLGQKYB3pB0Q8= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -1386,16 +1174,12 @@ github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -1405,16 +1189,10 @@ github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= -github.com/splitio/go-client/v6 v6.1.0/go.mod h1:CEGAEFT99Fwb32ZIRcnZoXTMXddtB6IIpTmt3RP8mnM= -github.com/splitio/go-split-commons/v3 v3.1.0/go.mod h1:29NCy20oAS4ZMy4qkwTd6277eieVDonx4V/aeDU/wUQ= -github.com/splitio/go-toolkit/v4 v4.2.0/go.mod h1:EdIHN0yzB1GTXDYQc0KdKvnjkO/jfUM2YqHVYfhD3Wo= -github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -1431,8 +1209,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -1440,7 +1219,6 @@ github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= -github.com/throttled/throttled v2.2.5+incompatible/go.mod h1:0BjlrEGQmvxps+HuXLsyRdqpSRvJpq0PNIsOtqP9Nos= github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo= github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -1454,25 +1232,11 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= -github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= @@ -1501,22 +1265,16 @@ github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g= github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM= -github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= 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/yuin/goldmark v1.4.12 h1:6hffw6vALvEDqJ19dOJvJKOoAOKe4NDaTqvd2sktGN0= github.com/yuin/goldmark v1.4.12/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= @@ -1550,7 +1308,6 @@ go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUz go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0/go.mod h1:vEhqr0m4eTc+DWxfsXoXue2GBgV2uUwVznkGIHW/e5w= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= -go.opentelemetry.io/otel v0.11.0/go.mod h1:G8UCk+KooF2HLkgo8RHX9epABH/aRGYET7gQOqBVdB0= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI= @@ -1574,7 +1331,7 @@ 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.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= @@ -1591,13 +1348,11 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/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-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/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-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1605,17 +1360,14 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -1635,18 +1387,15 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 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/exp v0.0.0-20200908183739-ae8ad444f925/go.mod h1:1phAWC201xIgDyaFpmDeZkgf70Q4Pd/CNqfRtVPtxNw= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 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-20190910094157-69e4b8554b2a/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-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20220601225756-64ec528b34cd/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 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= @@ -1668,7 +1417,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB 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.3.1-0.20200828183125-ce943fd02449/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= @@ -1676,7 +1424,6 @@ golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1693,7 +1440,6 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190225153610-fe579d43d832/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-20190313220215-9f648a60d977/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= @@ -1720,7 +1466,6 @@ golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/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-20200602114024-627f9648deb9/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= @@ -1743,16 +1488,12 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220614195744-fb05da6f9022 h1:0qjDla5xICC2suMtyRH/QqX3B1btXTfNsIt/i4LFgO0= golang.org/x/net v0.0.0-20220614195744-fb05da6f9022/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1770,7 +1511,6 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ 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-20210402161424-2e8d93401602/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= @@ -1790,6 +1530,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ 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/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1800,8 +1541,6 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/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-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/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= @@ -1830,7 +1569,6 @@ golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1913,7 +1651,6 @@ golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBc 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-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1924,7 +1661,6 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098 h1:PgOr27OhUx2IRqGJ2RxAWI4dJQ7bi9cSrB82uzFzfUA= golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -1948,7 +1684,6 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1959,14 +1694,12 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181221001348-537d06c36207/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-20190206041539-40960b6deb8e/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-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -2045,7 +1778,6 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= @@ -2077,7 +1809,6 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q 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.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= 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= @@ -2099,7 +1830,6 @@ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID 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/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= -google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -2179,7 +1909,6 @@ google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2I google.golang.org/genproto v0.0.0-20220614165028-45ed7f3ff16e h1:ubR4JUtqN3ffdFjpKylv8scWk/mZstGmzXbgYSkuMl0= google.golang.org/genproto v0.0.0-20220614165028-45ed7f3ff16e/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -2235,7 +1964,6 @@ google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscL google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -2250,14 +1978,8 @@ gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKW gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI= gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= -gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= @@ -2276,7 +1998,6 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/server/integrationtests/board_test.go b/server/integrationtests/board_test.go index 4219edce9..cecfe4bd3 100644 --- a/server/integrationtests/board_test.go +++ b/server/integrationtests/board_test.go @@ -1936,6 +1936,108 @@ func TestDuplicateBoard(t *testing.T) { require.Equal(t, duplicateBoard.ID, members[0].BoardID) require.True(t, members[0].SchemeAdmin) }) + + t.Run("create and duplicate public board from a custom category", func(t *testing.T) { + th := SetupTestHelper(t).InitBasic() + defer th.TearDown() + + me := th.GetUser1() + teamID := testTeamID + + category := model.Category{ + Name: "My Category", + UserID: me.ID, + TeamID: teamID, + } + createdCategory, resp := th.Client.CreateCategory(category) + th.CheckOK(resp) + require.NoError(t, resp.Error) + require.NotNil(t, createdCategory) + require.Equal(t, "My Category", createdCategory.Name) + require.Equal(t, me.ID, createdCategory.UserID) + require.Equal(t, teamID, createdCategory.TeamID) + + title := "Public board" + newBoard := &model.Board{ + Title: title, + Type: model.BoardTypeOpen, + TeamID: teamID, + } + board, resp := th.Client.CreateBoard(newBoard) + th.CheckOK(resp) + require.NoError(t, resp.Error) + require.NotNil(t, board) + require.NotNil(t, board.ID) + require.Equal(t, title, board.Title) + require.Equal(t, model.BoardTypeOpen, board.Type) + require.Equal(t, teamID, board.TeamID) + require.Equal(t, me.ID, board.CreatedBy) + require.Equal(t, me.ID, board.ModifiedBy) + + // move board to custom category + resp = th.Client.UpdateCategoryBoard(teamID, createdCategory.ID, board.ID) + th.CheckOK(resp) + require.NoError(t, resp.Error) + + newBlocks := []model.Block{ + { + ID: utils.NewID(utils.IDTypeBlock), + BoardID: board.ID, + CreateAt: 1, + UpdateAt: 1, + Title: "View 1", + Type: model.TypeView, + }, + } + + newBlocks, resp = th.Client.InsertBlocks(board.ID, newBlocks) + require.NoError(t, resp.Error) + require.Len(t, newBlocks, 1) + + newUserMember := &model.BoardMember{ + UserID: th.GetUser2().ID, + BoardID: board.ID, + SchemeEditor: true, + } + th.Client.AddMemberToBoard(newUserMember) + + members, err := th.Server.App().GetMembersForBoard(board.ID) + require.NoError(t, err) + require.Len(t, members, 2) + + // Duplicate the board + rBoardsAndBlock, resp := th.Client.DuplicateBoard(board.ID, false, teamID) + th.CheckOK(resp) + require.NotNil(t, rBoardsAndBlock) + require.Equal(t, len(rBoardsAndBlock.Boards), 1) + require.Equal(t, len(rBoardsAndBlock.Blocks), 1) + + duplicateBoard := rBoardsAndBlock.Boards[0] + require.Equal(t, duplicateBoard.Type, model.BoardTypePrivate, "Duplicated board should be private") + require.Equal(t, "Public board copy", duplicateBoard.Title) + + members, err = th.Server.App().GetMembersForBoard(duplicateBoard.ID) + require.NoError(t, err) + require.Len(t, members, 1, "Duplicated board should only have one member") + require.Equal(t, me.ID, members[0].UserID) + require.Equal(t, duplicateBoard.ID, members[0].BoardID) + require.True(t, members[0].SchemeAdmin) + + // verify duplicated board is in the same custom category + userCategoryBoards, resp := th.Client.GetUserCategoryBoards(teamID) + th.CheckOK(resp) + require.NotNil(t, rBoardsAndBlock) + + var duplicateBoardCategoryID string + for _, categoryBoard := range userCategoryBoards { + for _, boardID := range categoryBoard.BoardIDs { + if boardID == duplicateBoard.ID { + duplicateBoardCategoryID = categoryBoard.Category.ID + } + } + } + require.Equal(t, createdCategory.ID, duplicateBoardCategoryID) + }) } func TestJoinBoard(t *testing.T) { diff --git a/server/integrationtests/clienttestlib.go b/server/integrationtests/clienttestlib.go index 6037a9bf6..6ff518704 100644 --- a/server/integrationtests/clienttestlib.go +++ b/server/integrationtests/clienttestlib.go @@ -7,7 +7,6 @@ import ( "testing" "time" - "github.com/mattermost/focalboard/server/api" "github.com/mattermost/focalboard/server/client" "github.com/mattermost/focalboard/server/model" "github.com/mattermost/focalboard/server/server" @@ -363,7 +362,7 @@ func (th *TestHelper) TearDown() { } func (th *TestHelper) RegisterAndLogin(client *client.Client, username, email, password, token string) { - req := &api.RegisterRequest{ + req := &model.RegisterRequest{ Username: username, Email: email, Password: password, @@ -378,7 +377,7 @@ func (th *TestHelper) RegisterAndLogin(client *client.Client, username, email, p } func (th *TestHelper) Login(client *client.Client, username, password string) { - req := &api.LoginRequest{ + req := &model.LoginRequest{ Type: "normal", Username: username, Password: password, diff --git a/server/integrationtests/permissions_test.go b/server/integrationtests/permissions_test.go index 71b1a8472..c772a732f 100644 --- a/server/integrationtests/permissions_test.go +++ b/server/integrationtests/permissions_test.go @@ -10,7 +10,6 @@ import ( "strings" "testing" - "github.com/mattermost/focalboard/server/api" "github.com/mattermost/focalboard/server/client" "github.com/mattermost/focalboard/server/model" "github.com/stretchr/testify/require" @@ -370,6 +369,43 @@ func TestPermissionsSearchTeamBoards(t *testing.T) { }) } +func TestPermissionsSearchTeamLinkableBoards(t *testing.T) { + t.Run("plugin", func(t *testing.T) { + th := SetupTestHelperPluginMode(t) + defer th.TearDown() + clients := setupClients(th) + testData := setupData(t, th) + ttCases := []TestCase{ + // Search boards + {"/teams/test-team/boards/search/linkable?q=b", methodGet, "", userAnon, http.StatusUnauthorized, 0}, + {"/teams/test-team/boards/search/linkable?q=b", methodGet, "", userNoTeamMember, http.StatusForbidden, 0}, + {"/teams/test-team/boards/search/linkable?q=b", methodGet, "", userTeamMember, http.StatusOK, 0}, + {"/teams/test-team/boards/search/linkable?q=b", methodGet, "", userViewer, http.StatusOK, 0}, + {"/teams/test-team/boards/search/linkable?q=b", methodGet, "", userCommenter, http.StatusOK, 0}, + {"/teams/test-team/boards/search/linkable?q=b", methodGet, "", userEditor, http.StatusOK, 0}, + {"/teams/test-team/boards/search/linkable?q=b", methodGet, "", userAdmin, http.StatusOK, 2}, + } + runTestCases(t, ttCases, testData, clients) + }) + t.Run("local", func(t *testing.T) { + th := SetupTestHelperLocalMode(t) + defer th.TearDown() + clients := setupLocalClients(th) + testData := setupData(t, th) + ttCases := []TestCase{ + // Search boards + {"/teams/test-team/boards/search/linkable?q=b", methodGet, "", userAnon, http.StatusUnauthorized, 0}, + {"/teams/test-team/boards/search/linkable?q=b", methodGet, "", userNoTeamMember, http.StatusNotImplemented, 0}, + {"/teams/test-team/boards/search/linkable?q=b", methodGet, "", userTeamMember, http.StatusNotImplemented, 0}, + {"/teams/test-team/boards/search/linkable?q=b", methodGet, "", userViewer, http.StatusNotImplemented, 0}, + {"/teams/test-team/boards/search/linkable?q=b", methodGet, "", userCommenter, http.StatusNotImplemented, 0}, + {"/teams/test-team/boards/search/linkable?q=b", methodGet, "", userEditor, http.StatusNotImplemented, 0}, + {"/teams/test-team/boards/search/linkable?q=b", methodGet, "", userAdmin, http.StatusNotImplemented, 0}, + } + runTestCases(t, ttCases, testData, clients) + }) +} + func TestPermissionsGetTeamTemplates(t *testing.T) { extraSetup := func(t *testing.T, th *TestHelper) { err := th.Server.App().InitTemplates() @@ -2304,7 +2340,7 @@ func TestPermissionsGetUser(t *testing.T) { } func TestPermissionsUserChangePassword(t *testing.T) { - postBody := toJSON(t, api.ChangePasswordRequest{ + postBody := toJSON(t, model.ChangePasswordRequest{ OldPassword: password, NewPassword: "newpa$$word123", }) @@ -2512,7 +2548,7 @@ func TestPermissionsDeleteBoardsAndBlocks(t *testing.T) { func TestPermissionsLogin(t *testing.T) { loginReq := func(username, password string) string { - return toJSON(t, api.LoginRequest{ + return toJSON(t, model.LoginRequest{ Type: "normal", Username: username, Password: password, @@ -2591,7 +2627,7 @@ func TestPermissionsRegister(t *testing.T) { require.NotNil(th.T, team) require.NotNil(th.T, team.SignupToken) - postData := toJSON(t, api.RegisterRequest{ + postData := toJSON(t, model.RegisterRequest{ Username: "newuser", Email: "newuser@test.com", Password: password, diff --git a/server/integrationtests/user_test.go b/server/integrationtests/user_test.go index 10b76f235..01cb33d2a 100644 --- a/server/integrationtests/user_test.go +++ b/server/integrationtests/user_test.go @@ -5,7 +5,6 @@ import ( "crypto/rand" "testing" - "github.com/mattermost/focalboard/server/api" "github.com/mattermost/focalboard/server/model" "github.com/mattermost/focalboard/server/utils" "github.com/stretchr/testify/require" @@ -21,7 +20,7 @@ func TestUserRegister(t *testing.T) { defer th.TearDown() // register - registerRequest := &api.RegisterRequest{ + registerRequest := &model.RegisterRequest{ Username: fakeUsername, Email: fakeEmail, Password: utils.NewID(utils.IDTypeNone), @@ -41,7 +40,7 @@ func TestUserLogin(t *testing.T) { defer th.TearDown() t.Run("with nonexist user", func(t *testing.T) { - loginRequest := &api.LoginRequest{ + loginRequest := &model.LoginRequest{ Type: "normal", Username: "nonexistuser", Email: "", @@ -55,7 +54,7 @@ func TestUserLogin(t *testing.T) { t.Run("with registered user", func(t *testing.T) { password := utils.NewID(utils.IDTypeNone) // register - registerRequest := &api.RegisterRequest{ + registerRequest := &model.RegisterRequest{ Username: fakeUsername, Email: fakeEmail, Password: password, @@ -65,7 +64,7 @@ func TestUserLogin(t *testing.T) { require.True(t, success) // login - loginRequest := &api.LoginRequest{ + loginRequest := &model.LoginRequest{ Type: "normal", Username: fakeUsername, Email: fakeEmail, @@ -91,7 +90,7 @@ func TestGetMe(t *testing.T) { t.Run("logged in", func(t *testing.T) { // register password := utils.NewID(utils.IDTypeNone) - registerRequest := &api.RegisterRequest{ + registerRequest := &model.RegisterRequest{ Username: fakeUsername, Email: fakeEmail, Password: password, @@ -100,7 +99,7 @@ func TestGetMe(t *testing.T) { require.NoError(t, resp.Error) require.True(t, success) // login - loginRequest := &api.LoginRequest{ + loginRequest := &model.LoginRequest{ Type: "normal", Username: fakeUsername, Email: fakeEmail, @@ -126,7 +125,7 @@ func TestGetUser(t *testing.T) { // register password := utils.NewID(utils.IDTypeNone) - registerRequest := &api.RegisterRequest{ + registerRequest := &model.RegisterRequest{ Username: fakeUsername, Email: fakeEmail, Password: password, @@ -135,7 +134,7 @@ func TestGetUser(t *testing.T) { require.NoError(t, resp.Error) require.True(t, success) // login - loginRequest := &api.LoginRequest{ + loginRequest := &model.LoginRequest{ Type: "normal", Username: fakeUsername, Email: fakeEmail, @@ -171,7 +170,7 @@ func TestUserChangePassword(t *testing.T) { // register password := utils.NewID(utils.IDTypeNone) - registerRequest := &api.RegisterRequest{ + registerRequest := &model.RegisterRequest{ Username: fakeUsername, Email: fakeEmail, Password: password, @@ -180,7 +179,7 @@ func TestUserChangePassword(t *testing.T) { require.NoError(t, resp.Error) require.True(t, success) // login - loginRequest := &api.LoginRequest{ + loginRequest := &model.LoginRequest{ Type: "normal", Username: fakeUsername, Email: fakeEmail, @@ -196,7 +195,7 @@ func TestUserChangePassword(t *testing.T) { require.NotNil(t, originalMe) // change password - success, resp = th.Client.UserChangePassword(originalMe.ID, &api.ChangePasswordRequest{ + success, resp = th.Client.UserChangePassword(originalMe.ID, &model.ChangePasswordRequest{ OldPassword: password, NewPassword: utils.NewID(utils.IDTypeNone), }) diff --git a/server/main/main.go b/server/main/main.go index 22ef035a0..ba7298f91 100644 --- a/server/main/main.go +++ b/server/main/main.go @@ -53,16 +53,6 @@ func monitorPid(pid int, logger *mlog.Logger) { }() } -func logInfo(logger *mlog.Logger) { - logger.Info("FocalBoard Server", - mlog.String("version", model.CurrentVersion), - mlog.String("edition", model.Edition), - mlog.String("build_number", model.BuildNumber), - mlog.String("build_date", model.BuildDate), - mlog.String("build_hash", model.BuildHash), - ) -} - func main() { // Command line args pMonitorPid := flag.Int("monitorpid", -1, "a process ID") @@ -101,7 +91,7 @@ func main() { defer restore() } - logInfo(logger) + model.LogServerInfo(logger) singleUser := false if pSingleUser != nil { @@ -214,7 +204,7 @@ func startServer(webPath string, filesPath string, port int, singleUserToken, db return } - logInfo(logger) + model.LogServerInfo(logger) if len(filesPath) > 0 { config.FilesPath = filesPath diff --git a/server/model/auth.go b/server/model/auth.go new file mode 100644 index 000000000..fef187d45 --- /dev/null +++ b/server/model/auth.go @@ -0,0 +1,129 @@ +package model + +import ( + "encoding/json" + "fmt" + "io" + "strings" + + "github.com/mattermost/focalboard/server/services/auth" +) + +const ( + MinimumPasswordLength = 8 +) + +type AuthParamError struct { + msg string +} + +func (pe AuthParamError) Error() string { + return pe.msg +} + +// LoginRequest is a login request +// swagger:model +type LoginRequest struct { + // Type of login, currently must be set to "normal" + // required: true + Type string `json:"type"` + + // If specified, login using username + // required: false + Username string `json:"username"` + + // If specified, login using email + // required: false + Email string `json:"email"` + + // Password + // required: true + Password string `json:"password"` + + // MFA token + // required: false + // swagger:ignore + MfaToken string `json:"mfa_token"` +} + +// LoginResponse is a login response +// swagger:model +type LoginResponse struct { + // Session token + // required: true + Token string `json:"token"` +} + +func LoginResponseFromJSON(data io.Reader) (*LoginResponse, error) { + var resp LoginResponse + if err := json.NewDecoder(data).Decode(&resp); err != nil { + return nil, err + } + return &resp, nil +} + +// RegisterRequest is a user registration request +// swagger:model +type RegisterRequest struct { + // User name + // required: true + Username string `json:"username"` + + // User's email + // required: true + Email string `json:"email"` + + // Password + // required: true + Password string `json:"password"` + + // Registration authorization token + // required: true + Token string `json:"token"` +} + +func (rd *RegisterRequest) IsValid() error { + if strings.TrimSpace(rd.Username) == "" { + return AuthParamError{"username is required"} + } + if strings.TrimSpace(rd.Email) == "" { + return AuthParamError{"email is required"} + } + if !auth.IsEmailValid(rd.Email) { + return AuthParamError{"invalid email format"} + } + if rd.Password == "" { + return AuthParamError{"password is required"} + } + return isValidPassword(rd.Password) +} + +// ChangePasswordRequest is a user password change request +// swagger:model +type ChangePasswordRequest struct { + // Old password + // required: true + OldPassword string `json:"oldPassword"` + + // New password + // required: true + NewPassword string `json:"newPassword"` +} + +// IsValid validates a password change request. +func (rd *ChangePasswordRequest) IsValid() error { + if rd.OldPassword == "" { + return AuthParamError{"old password is required"} + } + if rd.NewPassword == "" { + return AuthParamError{"new password is required"} + } + return isValidPassword(rd.NewPassword) +} + +func isValidPassword(password string) error { + if len(password) < MinimumPasswordLength { + return AuthParamError{fmt.Sprintf("password must be at least %d characters", MinimumPasswordLength)} + } + return nil +} diff --git a/server/model/block.go b/server/model/block.go index 3bdd9eab7..d4048e115 100644 --- a/server/model/block.go +++ b/server/model/block.go @@ -94,10 +94,6 @@ type BlockPatch struct { // The block removed fields // required: false DeletedFields []string `json:"deletedFields"` - - // The board id that the block belongs to - // required: false - BoardID *string `json:"boardId"` } // BlockPatchBatch is a batch of IDs and patches for modify blocks @@ -149,10 +145,6 @@ func (p *BlockPatch) Patch(block *Block) *Block { block.ParentID = *p.ParentID } - if p.BoardID != nil { - block.BoardID = *p.BoardID - } - if p.Schema != nil { block.Schema = *p.Schema } diff --git a/server/model/block_test.go b/server/model/block_test.go index 32c563688..2ddb06690 100644 --- a/server/model/block_test.go +++ b/server/model/block_test.go @@ -253,6 +253,46 @@ func TestGenerateBlockIDs(t *testing.T) { require.Equal(t, blocks[1].ID, block4ContentOrder[1].([]interface{})[0]) require.Equal(t, blocks[2].ID, block4ContentOrder[1].([]interface{})[1]) }) + + t.Run("Should update Id of default template view", func(t *testing.T) { + blockID1 := utils.NewID(utils.IDTypeBlock) + boardID1 := utils.NewID(utils.IDTypeBlock) + parentID1 := utils.NewID(utils.IDTypeBlock) + block1 := Block{ + ID: blockID1, + BoardID: boardID1, + ParentID: parentID1, + } + + blockID2 := utils.NewID(utils.IDTypeBlock) + boardID2 := utils.NewID(utils.IDTypeBlock) + parentID2 := utils.NewID(utils.IDTypeBlock) + block2 := Block{ + ID: blockID2, + BoardID: boardID2, + ParentID: parentID2, + Fields: map[string]interface{}{ + "defaultTemplateId": blockID1, + }, + } + + blocks := []Block{block1, block2} + + blocks = GenerateBlockIDs(blocks, &mlog.Logger{}) + + require.NotEqual(t, blockID1, blocks[0].ID) + require.Equal(t, boardID1, blocks[0].BoardID) + require.Equal(t, parentID1, blocks[0].ParentID) + + require.NotEqual(t, blockID2, blocks[1].ID) + require.Equal(t, boardID2, blocks[1].BoardID) + require.Equal(t, parentID2, blocks[1].ParentID) + + block2DefaultTemplateID, ok := block2.Fields["defaultTemplateId"].(string) + require.True(t, ok) + require.NotEqual(t, blockID1, block2DefaultTemplateID) + require.Equal(t, blocks[0].ID, block2DefaultTemplateID) + }) } func TestStampModificationMetadata(t *testing.T) { diff --git a/server/model/blockid.go b/server/model/blockid.go index fa466f009..f8994c1bd 100644 --- a/server/model/blockid.go +++ b/server/model/blockid.go @@ -52,6 +52,21 @@ func GenerateBlockIDs(blocks []Block, logger mlog.LoggerIFace) []Block { } } } + + if _, ok := block.Fields["defaultTemplateId"]; ok { + defaultTemplateID, typeOk := block.Fields["defaultTemplateId"].(string) + if !typeOk { + logger.Warn( + "type assertion failed for default template ID when saving reference block IDs", + mlog.String("blockID", block.ID), + mlog.String("actionType", fmt.Sprintf("%T", block.Fields["defaultTemplateId"])), + mlog.String("expectedType", "string"), + mlog.String("defaultTemplateId", fmt.Sprintf("%v", block.Fields["defaultTemplateId"])), + ) + continue + } + referenceIDs[defaultTemplateID] = true + } } newIDs := map[string]string{} @@ -93,6 +108,21 @@ func GenerateBlockIDs(blocks []Block, logger mlog.LoggerIFace) []Block { fixFieldIDs(&blockMod, "cardOrder", getExistingOrOldID, logger) } + if _, ok := blockMod.Fields["defaultTemplateId"]; ok { + defaultTemplateID, typeOk := blockMod.Fields["defaultTemplateId"].(string) + if !typeOk { + logger.Warn( + "type assertion failed for default template ID when saving reference block IDs", + mlog.String("blockID", blockMod.ID), + mlog.String("actionType", fmt.Sprintf("%T", blockMod.Fields["defaultTemplateId"])), + mlog.String("expectedType", "string"), + mlog.String("defaultTemplateId", fmt.Sprintf("%v", blockMod.Fields["defaultTemplateId"])), + ) + } else { + blockMod.Fields["defaultTemplateId"] = getExistingOrOldID(defaultTemplateID) + } + } + newBlocks[i] = blockMod } diff --git a/server/model/board_insights.go b/server/model/board_insights.go new file mode 100644 index 000000000..d4beaf04d --- /dev/null +++ b/server/model/board_insights.go @@ -0,0 +1,62 @@ +package model + +import ( + "encoding/json" + "io" + + mmModel "github.com/mattermost/mattermost-server/v6/model" +) + +// BoardInsightsList is a response type with pagination support. +type BoardInsightsList struct { + mmModel.InsightsListData + Items []*BoardInsight `json:"items"` +} + +// BoardInsight gives insight into activities in a Board +// swagger:model +type BoardInsight struct { + // ID of the board + // required: true + BoardID string `json:"boardID"` + + // icon of the board + // required: false + Icon string `json:"icon"` + + // Title of the board + // required: false + Title string `json:"title"` + + // Metric of how active the board is + // required: true + ActivityCount string `json:"activityCount"` + + // IDs of users active on the board + // required: true + ActiveUsers string `json:"activeUsers"` + + // ID of user who created the board + // required: true + CreatedBy string `json:"createdBy"` +} + +func BoardInsightsFromJSON(data io.Reader) []BoardInsight { + var boardInsights []BoardInsight + _ = json.NewDecoder(data).Decode(&boardInsights) + return boardInsights +} + +// GetTopBoardInsightsListWithPagination adds a rank to each item in the given list of BoardInsight and checks if there is +// another page that can be fetched based on the given limit and offset. The given list of BoardInsight is assumed to be +// sorted by ActivityCount(score). Returns a BoardInsightsList. +func GetTopBoardInsightsListWithPagination(boards []*BoardInsight, limit int) *BoardInsightsList { + // Add pagination support + var hasNext bool + if limit != 0 && len(boards) == limit+1 { + hasNext = true + boards = boards[:len(boards)-1] + } + + return &BoardInsightsList{InsightsListData: mmModel.InsightsListData{HasNext: hasNext}, Items: boards} +} diff --git a/server/model/category.go b/server/model/category.go index c1686f433..4ec582638 100644 --- a/server/model/category.go +++ b/server/model/category.go @@ -1,6 +1,8 @@ package model import ( + "encoding/json" + "io" "strings" "github.com/mattermost/focalboard/server/utils" @@ -36,6 +38,10 @@ type Category struct { // The deleted time in miliseconds since the current epoch. Set to indicate this category is deleted // required: false DeleteAt int64 `json:"deleteAt"` + + // Category's state in client side + // required: true + Collapsed bool `json:"collapsed"` } func (c *Category) Hydrate() { @@ -77,3 +83,9 @@ func newErrInvalidCategory(msg string) *ErrInvalidCategory { func (e *ErrInvalidCategory) Error() string { return e.msg } + +func CategoryFromJSON(data io.Reader) *Category { + var category *Category + _ = json.NewDecoder(data).Decode(&category) + return category +} diff --git a/server/model/error.go b/server/model/error.go index dd03e068b..386b5f5a0 100644 --- a/server/model/error.go +++ b/server/model/error.go @@ -6,7 +6,7 @@ import ( "fmt" "strings" - apierrors "github.com/mattermost/mattermost-plugin-api/errors" + pluginapi "github.com/mattermost/mattermost-plugin-api" ) // ErrBlocksFromDifferentBoards is an error type that can be returned @@ -32,7 +32,7 @@ func (nf *ErrNotFound) Error() string { // IsErrNotFound returns true if `err` is or wraps one of: // - model.ErrNotFound // - sql.ErrNoRows -// - mattermost-plugin-api/errors/ErrNotFound. +// - mattermost-plugin-api/ErrNotFound. func IsErrNotFound(err error) bool { if err == nil { return false @@ -50,7 +50,7 @@ func IsErrNotFound(err error) bool { } // check if this is a plugin API error - return errors.Is(err, apierrors.ErrNotFound) + return errors.Is(err, pluginapi.ErrNotFound) } // ErrNotAllFound is an error type that can be returned by store APIs diff --git a/server/model/mocks/mockservicesapi.go b/server/model/mocks/mockservicesapi.go index c2b1b7030..257b8ff60 100644 --- a/server/model/mocks/mockservicesapi.go +++ b/server/model/mocks/mockservicesapi.go @@ -9,6 +9,7 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" + mux "github.com/gorilla/mux" model "github.com/mattermost/mattermost-server/v6/model" mlog "github.com/mattermost/mattermost-server/v6/shared/mlog" ) @@ -386,6 +387,18 @@ func (mr *MockServicesAPIMockRecorder) PublishWebSocketEvent(arg0, arg1, arg2 in return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishWebSocketEvent", reflect.TypeOf((*MockServicesAPI)(nil).PublishWebSocketEvent), arg0, arg1, arg2) } +// RegisterRouter mocks base method. +func (m *MockServicesAPI) RegisterRouter(arg0 *mux.Router) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RegisterRouter", arg0) +} + +// RegisterRouter indicates an expected call of RegisterRouter. +func (mr *MockServicesAPIMockRecorder) RegisterRouter(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterRouter", reflect.TypeOf((*MockServicesAPI)(nil).RegisterRouter), arg0) +} + // UpdateUser mocks base method. func (m *MockServicesAPI) UpdateUser(arg0 *model.User) (*model.User, error) { m.ctrl.T.Helper() diff --git a/server/model/services_api.go b/server/model/services_api.go index d7fa0f179..dcade8757 100644 --- a/server/model/services_api.go +++ b/server/model/services_api.go @@ -8,6 +8,8 @@ package model import ( "database/sql" + "github.com/gorilla/mux" + mm_model "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/shared/mlog" ) @@ -67,4 +69,7 @@ type ServicesAPI interface { // System service GetDiagnosticID() string + + // Router service + RegisterRouter(sub *mux.Router) } diff --git a/server/model/version.go b/server/model/version.go index d633b94ff..abdcef3d8 100644 --- a/server/model/version.go +++ b/server/model/version.go @@ -1,5 +1,9 @@ package model +import ( + "github.com/mattermost/mattermost-server/v6/shared/mlog" +) + // This is a list of all the current versions including any patches. // It should be maintained in chronological order with most current // release at the front of the list. @@ -41,3 +45,14 @@ var ( BuildHash string Edition string ) + +// LogServerInfo logs information about the server instance. +func LogServerInfo(logger mlog.LoggerIFace) { + logger.Info("FocalBoard Server", + mlog.String("version", CurrentVersion), + mlog.String("edition", Edition), + mlog.String("build_number", BuildNumber), + mlog.String("build_date", BuildDate), + mlog.String("build_hash", BuildHash), + ) +} diff --git a/server/services/permissions/mmpermissions/mocks/mockpluginapi.go b/server/services/permissions/mmpermissions/mocks/mockpluginapi.go index b206e8383..26bb2578d 100644 --- a/server/services/permissions/mmpermissions/mocks/mockpluginapi.go +++ b/server/services/permissions/mmpermissions/mocks/mockpluginapi.go @@ -681,21 +681,6 @@ func (mr *MockAPIMockRecorder) GetChannelsForTeamForUser(arg0, arg1, arg2 interf return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChannelsForTeamForUser", reflect.TypeOf((*MockAPI)(nil).GetChannelsForTeamForUser), arg0, arg1, arg2) } -// GetCloudLimits mocks base method. -func (m *MockAPI) GetCloudLimits() (*model.ProductLimits, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetCloudLimits") - ret0, _ := ret[0].(*model.ProductLimits) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetCloudLimits indicates an expected call of GetCloudLimits. -func (mr *MockAPIMockRecorder) GetCloudLimits() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCloudLimits", reflect.TypeOf((*MockAPI)(nil).GetCloudLimits)) -} - // GetCommand mocks base method. func (m *MockAPI) GetCommand(arg0 string) (*model.Command, error) { m.ctrl.T.Helper() diff --git a/server/services/store/mattermostauthlayer/mattermostauthlayer.go b/server/services/store/mattermostauthlayer/mattermostauthlayer.go index 05996b60a..52ad35152 100644 --- a/server/services/store/mattermostauthlayer/mattermostauthlayer.go +++ b/server/services/store/mattermostauthlayer/mattermostauthlayer.go @@ -275,8 +275,8 @@ func (s *MattermostAuthLayer) GetUsersByTeam(teamID string) ([]*model.User, erro Select("u.id", "u.username", "u.email", "u.nickname", "u.firstname", "u.lastname", "u.props", "u.CreateAt as create_at", "u.UpdateAt as update_at", "u.DeleteAt as delete_at", "b.UserId IS NOT NULL AS is_bot"). From("Users as u"). - Join("TeamMembers as tm ON tm.UserID = u.ID"). - LeftJoin("Bots b ON ( b.UserId = Users.ID )"). + Join("TeamMembers as tm ON tm.UserID = u.id"). + LeftJoin("Bots b ON ( b.UserID = u.id )"). Where(sq.Eq{"u.deleteAt": 0}). Where(sq.NotEq{"u.roles": "system_guest"}). Where(sq.Eq{"tm.TeamId": teamID}) @@ -295,6 +295,28 @@ func (s *MattermostAuthLayer) GetUsersByTeam(teamID string) ([]*model.User, erro return users, nil } +func (s *MattermostAuthLayer) GetUsersList(userIDs []string) ([]*model.User, error) { + query := s.getQueryBuilder(). + Select("u.id", "u.username", "u.email", "u.nickname", "u.firstname", "u.lastname", "u.props", "u.CreateAt as create_at", "u.UpdateAt as update_at", + "u.DeleteAt as delete_at", "b.UserId IS NOT NULL AS is_bot"). + From("Users as u"). + LeftJoin("Bots b ON ( b.UserId = u.id )"). + Where(sq.Eq{"u.id": userIDs}) + + rows, err := query.Query() + if err != nil { + return nil, err + } + defer s.CloseRows(rows) + + users, err := s.usersFromRows(rows) + if err != nil { + return nil, err + } + + return users, nil +} + func (s *MattermostAuthLayer) SearchUsersByTeam(teamID string, searchQuery string) ([]*model.User, error) { query := s.getQueryBuilder(). Select("u.id", "u.username", "u.email", "u.nickname", "u.firstname", "u.lastname", "u.props", "u.CreateAt as create_at", "u.UpdateAt as update_at", @@ -546,6 +568,7 @@ func (s *MattermostAuthLayer) SearchBoardsForUser(term, userID string) ([]*model From(s.tablePrefix + "boards as b"). LeftJoin(s.tablePrefix + "board_members as bm on b.id=bm.board_id"). LeftJoin("TeamMembers as tm on tm.teamid=b.team_id"). + LeftJoin("ChannelMembers as cm on cm.channelId=b.channel_id"). Where(sq.Eq{"b.is_template": false}). Where(sq.Eq{"tm.userID": userID}). Where(sq.Eq{"tm.deleteAt": 0}). @@ -553,18 +576,21 @@ func (s *MattermostAuthLayer) SearchBoardsForUser(term, userID string) ([]*model sq.Eq{"b.type": model.BoardTypeOpen}, sq.And{ sq.Eq{"b.type": model.BoardTypePrivate}, - sq.Eq{"bm.user_id": userID}, + sq.Or{ + sq.Eq{"bm.user_id": userID}, + sq.Eq{"cm.userId": userID}, + }, }, }) if term != "" { // break search query into space separated words - // and search for each word. + // and search for all words. // This should later be upgraded to industrial-strength // word tokenizer, that uses much more than space // to break words. - conditions := sq.Or{} + conditions := sq.And{} for _, word := range strings.Split(strings.TrimSpace(term), " ") { conditions = append(conditions, sq.Like{"lower(b.title)": "%" + strings.ToLower(word) + "%"}) @@ -662,15 +688,23 @@ func (s *MattermostAuthLayer) implicitBoardMembershipsFromRows(rows *sql.Rows) ( func (s *MattermostAuthLayer) GetMemberForBoard(boardID, userID string) (*model.BoardMember, error) { bm, err := s.Store.GetMemberForBoard(boardID, userID) if model.IsErrNotFound(err) { - b, err := s.Store.GetBoard(boardID) - if err != nil { - return nil, err + b, boardErr := s.Store.GetBoard(boardID) + if boardErr != nil { + return nil, boardErr } if b.ChannelID != "" { - _, err := s.servicesAPI.GetChannelMember(b.ChannelID, userID) - if err != nil { - return nil, err + _, memberErr := s.servicesAPI.GetChannelMember(b.ChannelID, userID) + if memberErr != nil { + var appErr *mmModel.AppError + if errors.As(memberErr, &appErr) && appErr.StatusCode == http.StatusNotFound { + // Plugin API returns error if channel member doesn't exist. + // We're fine if it doesn't exist, so its not an error for us. + return nil, model.NewErrNotFound(userID) + } + + return nil, memberErr } + return &model.BoardMember{ BoardID: boardID, UserID: userID, @@ -683,6 +717,9 @@ func (s *MattermostAuthLayer) GetMemberForBoard(boardID, userID string) (*model. }, nil } } + if err != nil { + return nil, err + } return bm, nil } @@ -789,13 +826,14 @@ func (s *MattermostAuthLayer) SearchUserChannels(teamID, userID, query string) ( if err != nil { return nil, err } + lowerQuery := strings.ToLower(query) result := []*mmModel.Channel{} count := 0 for _, channel := range channels { if channel.Type != mmModel.ChannelTypeDirect && channel.Type != mmModel.ChannelTypeGroup && - (strings.Contains(channel.Name, query) || strings.Contains(channel.DisplayName, query)) { + (strings.Contains(strings.ToLower(channel.Name), lowerQuery) || strings.Contains(strings.ToLower(channel.DisplayName), lowerQuery)) { result = append(result, channel) count++ if count >= 10 { @@ -861,3 +899,12 @@ func (s *MattermostAuthLayer) SendMessage(message, postType string, receipts []s return nil } + +func (s *MattermostAuthLayer) GetUserTimezone(userID string) (string, error) { + user, err := s.servicesAPI.GetUserByID(userID) + if err != nil { + return "", err + } + timezone := user.Timezone + return mmModel.GetPreferredTimezone(timezone), nil +} diff --git a/server/services/store/mockstore/mockstore.go b/server/services/store/mockstore/mockstore.go index 91e40677c..c56fa17ba 100644 --- a/server/services/store/mockstore/mockstore.go +++ b/server/services/store/mockstore/mockstore.go @@ -925,6 +925,21 @@ func (mr *MockStoreMockRecorder) GetTeam(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTeam", reflect.TypeOf((*MockStore)(nil).GetTeam), arg0) } +// GetTeamBoardsInsights mocks base method. +func (m *MockStore) GetTeamBoardsInsights(arg0, arg1 string, arg2 int64, arg3, arg4 int, arg5 []string) (*model.BoardInsightsList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTeamBoardsInsights", arg0, arg1, arg2, arg3, arg4, arg5) + ret0, _ := ret[0].(*model.BoardInsightsList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTeamBoardsInsights indicates an expected call of GetTeamBoardsInsights. +func (mr *MockStoreMockRecorder) GetTeamBoardsInsights(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTeamBoardsInsights", reflect.TypeOf((*MockStore)(nil).GetTeamBoardsInsights), arg0, arg1, arg2, arg3, arg4, arg5) +} + // GetTeamCount mocks base method. func (m *MockStore) GetTeamCount() (int64, error) { m.ctrl.T.Helper() @@ -985,6 +1000,21 @@ func (mr *MockStoreMockRecorder) GetUsedCardsCount() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUsedCardsCount", reflect.TypeOf((*MockStore)(nil).GetUsedCardsCount)) } +// GetUserBoardsInsights mocks base method. +func (m *MockStore) GetUserBoardsInsights(arg0, arg1 string, arg2 int64, arg3, arg4 int, arg5 []string) (*model.BoardInsightsList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserBoardsInsights", arg0, arg1, arg2, arg3, arg4, arg5) + ret0, _ := ret[0].(*model.BoardInsightsList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserBoardsInsights indicates an expected call of GetUserBoardsInsights. +func (mr *MockStoreMockRecorder) GetUserBoardsInsights(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserBoardsInsights", reflect.TypeOf((*MockStore)(nil).GetUserBoardsInsights), arg0, arg1, arg2, arg3, arg4, arg5) +} + // GetUserByEmail mocks base method. func (m *MockStore) GetUserByEmail(arg0 string) (*model.User, error) { m.ctrl.T.Helper() @@ -1045,6 +1075,21 @@ func (mr *MockStoreMockRecorder) GetUserCategoryBoards(arg0, arg1 interface{}) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserCategoryBoards", reflect.TypeOf((*MockStore)(nil).GetUserCategoryBoards), arg0, arg1) } +// GetUserTimezone mocks base method. +func (m *MockStore) GetUserTimezone(arg0 string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserTimezone", arg0) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserTimezone indicates an expected call of GetUserTimezone. +func (mr *MockStoreMockRecorder) GetUserTimezone(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserTimezone", reflect.TypeOf((*MockStore)(nil).GetUserTimezone), arg0) +} + // GetUsersByTeam mocks base method. func (m *MockStore) GetUsersByTeam(arg0 string) ([]*model.User, error) { m.ctrl.T.Helper() @@ -1060,6 +1105,21 @@ func (mr *MockStoreMockRecorder) GetUsersByTeam(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUsersByTeam", reflect.TypeOf((*MockStore)(nil).GetUsersByTeam), arg0) } +// GetUsersList mocks base method. +func (m *MockStore) GetUsersList(arg0 []string) ([]*model.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUsersList", arg0) + ret0, _ := ret[0].([]*model.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUsersList indicates an expected call of GetUsersList. +func (mr *MockStoreMockRecorder) GetUsersList(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUsersList", reflect.TypeOf((*MockStore)(nil).GetUsersList), arg0) +} + // InsertBlock mocks base method. func (m *MockStore) InsertBlock(arg0 *model.Block, arg1 string) error { m.ctrl.T.Helper() diff --git a/server/services/store/sqlstore/blocks.go b/server/services/store/sqlstore/blocks.go index 4465fc6d8..86b2bf695 100644 --- a/server/services/store/sqlstore/blocks.go +++ b/server/services/store/sqlstore/blocks.go @@ -126,23 +126,6 @@ func (s *SQLStore) getBlocksByIDs(db sq.BaseRunner, ids []string) ([]model.Block return blocks, nil } -func (s *SQLStore) getBlocksWithBoardID(db sq.BaseRunner, boardID string) ([]model.Block, error) { - query := s.getQueryBuilder(db). - Select(s.blockFields()...). - From(s.tablePrefix + "blocks"). - Where(sq.Eq{"board_id": boardID}) - - rows, err := query.Query() - if err != nil { - s.logger.Error(`GetBlocksWithBoardID ERROR`, mlog.Err(err)) - - return nil, err - } - defer s.CloseRows(rows) - - return s.blocksFromRows(rows) -} - func (s *SQLStore) getBlocksWithType(db sq.BaseRunner, boardID, blockType string) ([]model.Block, error) { query := s.getQueryBuilder(db). Select(s.blockFields()...). diff --git a/server/services/store/sqlstore/board.go b/server/services/store/sqlstore/board.go index 1a88a53b4..928086be4 100644 --- a/server/services/store/sqlstore/board.go +++ b/server/services/store/sqlstore/board.go @@ -660,12 +660,12 @@ func (s *SQLStore) searchBoardsForUser(db sq.BaseRunner, term, userID string) ([ if term != "" { // break search query into space separated words - // and search for each word. + // and search for all words. // This should later be upgraded to industrial-strength // word tokenizer, that uses much more than space // to break words. - conditions := sq.Or{} + conditions := sq.And{} for _, word := range strings.Split(strings.TrimSpace(term), " ") { conditions = append(conditions, sq.Like{"lower(b.title)": "%" + strings.ToLower(word) + "%"}) @@ -706,12 +706,12 @@ func (s *SQLStore) searchBoardsForUserInTeam(db sq.BaseRunner, teamID, term, use if term != "" { // break search query into space separated words - // and search for each word. + // and search for all words. // This should later be upgraded to industrial-strength // word tokenizer, that uses much more than space // to break words. - conditions := sq.Or{} + conditions := sq.And{} for _, word := range strings.Split(strings.TrimSpace(term), " ") { conditions = append(conditions, sq.Like{"lower(b.title)": "%" + strings.ToLower(word) + "%"}) diff --git a/server/services/store/sqlstore/board_insights.go b/server/services/store/sqlstore/board_insights.go new file mode 100644 index 000000000..f98e662be --- /dev/null +++ b/server/services/store/sqlstore/board_insights.go @@ -0,0 +1,147 @@ +package sqlstore + +import ( + "database/sql" + "fmt" + "time" + + "github.com/mattermost/focalboard/server/model" + + sq "github.com/Masterminds/squirrel" + mmModel "github.com/mattermost/mattermost-server/v6/model" + + "github.com/mattermost/mattermost-server/v6/shared/mlog" +) + +func (s *SQLStore) getTeamBoardsInsights(db sq.BaseRunner, teamID string, userID string, since int64, offset int, limit int, boardIDs []string) (*model.BoardInsightsList, error) { + boardsHistoryQuery := s.getQueryBuilder(db). + Select("boards.id, boards.icon, boards.title, count(boards_history.id) as count, boards_history.modified_by, boards.created_by"). + From(s.tablePrefix + "boards_history as boards_history"). + Join(s.tablePrefix + "boards as boards on boards_history.id = boards.id"). + Where(sq.Gt{"boards_history.insert_at": mmModel.GetTimeForMillis(since).Format(time.RFC3339)}). + Where(sq.Eq{"boards.team_id": teamID}). + Where(sq.Eq{"boards.id": boardIDs}). + Where(sq.NotEq{"boards_history.modified_by": "system"}). + Where(sq.Eq{"boards.delete_at": 0}). + GroupBy("boards.id, boards_history.id, boards_history.modified_by") + + blocksHistoryQuery := s.getQueryBuilder(db). + Select("boards.id, boards.icon, boards.title, count(blocks_history.id) as count, blocks_history.modified_by, boards.created_by"). + Prefix("UNION ALL"). + From(s.tablePrefix + "blocks_history as blocks_history"). + Join(s.tablePrefix + "boards as boards on blocks_history.board_id = boards.id"). + Where(sq.Gt{"blocks_history.insert_at": mmModel.GetTimeForMillis(since).Format(time.RFC3339)}). + Where(sq.Eq{"boards.team_id": teamID}). + Where(sq.Eq{"boards.id": boardIDs}). + Where(sq.NotEq{"blocks_history.modified_by": "system"}). + Where(sq.Eq{"boards.delete_at": 0}). + GroupBy("boards.id, blocks_history.board_id, blocks_history.modified_by") + + boardsActivity := boardsHistoryQuery.SuffixExpr(blocksHistoryQuery) + + insightsQuery := s.getQueryBuilder(db).Select( + fmt.Sprintf("id, title, icon, sum(count) as activity_count, %s as active_users, created_by", s.concatenationSelector("distinct modified_by", ",")), + ). + FromSelect(boardsActivity, "boards_and_blocks_history"). + GroupBy("id, title, icon, created_by"). + OrderBy("activity_count desc"). + Offset(uint64(offset)). + Limit(uint64(limit)) + + rows, err := insightsQuery.Query() + if err != nil { + s.logger.Error(`Team insights query ERROR`, mlog.Err(err)) + return nil, err + } + defer s.CloseRows(rows) + + boardsInsights, err := boardsInsightsFromRows(rows) + if err != nil { + return nil, err + } + boardInsightsPaginated := model.GetTopBoardInsightsListWithPagination(boardsInsights, limit) + + return boardInsightsPaginated, nil +} + +func (s *SQLStore) getUserBoardsInsights(db sq.BaseRunner, teamID string, userID string, since int64, offset int, limit int, boardIDs []string) (*model.BoardInsightsList, error) { + boardsHistoryQuery := s.getQueryBuilder(db). + Select("boards.id, boards.icon, boards.title, count(boards_history.id) as count, boards_history.modified_by, boards.created_by"). + From(s.tablePrefix + "boards_history as boards_history"). + Join(s.tablePrefix + "boards as boards on boards_history.id = boards.id"). + Where(sq.Gt{"boards_history.insert_at": mmModel.GetTimeForMillis(since).Format(time.RFC3339)}). + Where(sq.Eq{"boards.team_id": teamID}). + Where(sq.Eq{"boards.id": boardIDs}). + Where(sq.NotEq{"boards_history.modified_by": "system"}). + Where(sq.Eq{"boards.delete_at": 0}). + GroupBy("boards.id, boards_history.id, boards_history.modified_by") + + blocksHistoryQuery := s.getQueryBuilder(db). + Select("boards.id, boards.icon, boards.title, count(blocks_history.id) as count, blocks_history.modified_by, boards.created_by"). + Prefix("UNION ALL"). + From(s.tablePrefix + "blocks_history as blocks_history"). + Join(s.tablePrefix + "boards as boards on blocks_history.board_id = boards.id"). + Where(sq.Gt{"blocks_history.insert_at": mmModel.GetTimeForMillis(since).Format(time.RFC3339)}). + Where(sq.Eq{"boards.team_id": teamID}). + Where(sq.Eq{"boards.id": boardIDs}). + Where(sq.NotEq{"blocks_history.modified_by": "system"}). + Where(sq.Eq{"boards.delete_at": 0}). + GroupBy("boards.id, blocks_history.board_id, blocks_history.modified_by") + + boardsActivity := boardsHistoryQuery.SuffixExpr(blocksHistoryQuery) + + insightsQuery := s.getQueryBuilder(db).Select( + fmt.Sprintf("id, title, icon, sum(count) as activity_count, %s as active_users, created_by", s.concatenationSelector("distinct modified_by", ",")), + ). + FromSelect(boardsActivity, "boards_and_blocks_history"). + GroupBy("id, title, icon, created_by"). + OrderBy("activity_count desc") + + userQuery := s.getQueryBuilder(db).Select("*"). + FromSelect(insightsQuery, "boards_and_blocks_history_for_user"). + Where(sq.Or{ + sq.Eq{ + "created_by": userID, + }, + sq.Expr(s.elementInColumn("active_users"), userID), + }). + Offset(uint64(offset)). + Limit(uint64(limit)) + + rows, err := userQuery.Query() + + if err != nil { + s.logger.Error(`Team insights query ERROR`, mlog.Err(err)) + return nil, err + } + defer s.CloseRows(rows) + + boardsInsights, err := boardsInsightsFromRows(rows) + if err != nil { + return nil, err + } + boardInsightsPaginated := model.GetTopBoardInsightsListWithPagination(boardsInsights, limit) + + return boardInsightsPaginated, nil +} + +func boardsInsightsFromRows(rows *sql.Rows) ([]*model.BoardInsight, error) { + boardsInsights := []*model.BoardInsight{} + for rows.Next() { + var boardInsight model.BoardInsight + + err := rows.Scan( + &boardInsight.BoardID, + &boardInsight.Title, + &boardInsight.Icon, + &boardInsight.ActivityCount, + &boardInsight.ActiveUsers, + &boardInsight.CreatedBy, + ) + if err != nil { + return nil, err + } + boardsInsights = append(boardsInsights, &boardInsight) + } + return boardsInsights, nil +} diff --git a/server/services/store/sqlstore/boards_and_blocks.go b/server/services/store/sqlstore/boards_and_blocks.go index f8982504c..afb1097ad 100644 --- a/server/services/store/sqlstore/boards_and_blocks.go +++ b/server/services/store/sqlstore/boards_and_blocks.go @@ -161,7 +161,7 @@ func (s *SQLStore) duplicateBoard(db sq.BaseRunner, boardID string, userID strin } bab.Boards = []*model.Board{board} - blocks, err := s.getBlocksWithBoardID(db, boardID) + blocks, err := s.getBlocksForBoard(db, boardID) if err != nil { return nil, nil, err } diff --git a/server/services/store/sqlstore/category.go b/server/services/store/sqlstore/category.go index 5d4d86344..2f5211494 100644 --- a/server/services/store/sqlstore/category.go +++ b/server/services/store/sqlstore/category.go @@ -12,7 +12,7 @@ import ( func (s *SQLStore) getCategory(db sq.BaseRunner, id string) (*model.Category, error) { query := s.getQueryBuilder(db). - Select("id", "name", "user_id", "team_id", "create_at", "update_at", "delete_at"). + Select("id", "name", "user_id", "team_id", "create_at", "update_at", "delete_at", "collapsed"). From(s.tablePrefix + "categories"). Where(sq.Eq{"id": id}) @@ -46,6 +46,7 @@ func (s *SQLStore) createCategory(db sq.BaseRunner, category model.Category) err "create_at", "update_at", "delete_at", + "collapsed", ). Values( category.ID, @@ -55,6 +56,7 @@ func (s *SQLStore) createCategory(db sq.BaseRunner, category model.Category) err category.CreateAt, category.UpdateAt, category.DeleteAt, + category.Collapsed, ) _, err := query.Exec() @@ -70,6 +72,7 @@ func (s *SQLStore) updateCategory(db sq.BaseRunner, category model.Category) err Update(s.tablePrefix+"categories"). Set("name", category.Name). Set("update_at", category.UpdateAt). + Set("collapsed", category.Collapsed). Where(sq.Eq{"id": category.ID}) _, err := query.Exec() @@ -106,7 +109,7 @@ func (s *SQLStore) deleteCategory(db sq.BaseRunner, categoryID, userID, teamID s func (s *SQLStore) getUserCategories(db sq.BaseRunner, userID, teamID string) ([]model.Category, error) { query := s.getQueryBuilder(db). - Select("id", "name", "user_id", "team_id", "create_at", "update_at", "delete_at"). + Select("id", "name", "user_id", "team_id", "create_at", "update_at", "delete_at", "collapsed"). From(s.tablePrefix + "categories"). Where(sq.Eq{ "user_id": userID, @@ -136,6 +139,7 @@ func (s *SQLStore) categoriesFromRows(rows *sql.Rows) ([]model.Category, error) &category.CreateAt, &category.UpdateAt, &category.DeleteAt, + &category.Collapsed, ) if err != nil { diff --git a/server/services/store/sqlstore/data_migrations.go b/server/services/store/sqlstore/data_migrations.go index c4fb552ac..558cddfeb 100644 --- a/server/services/store/sqlstore/data_migrations.go +++ b/server/services/store/sqlstore/data_migrations.go @@ -14,10 +14,16 @@ import ( ) const ( - TemplatesToTeamsMigrationKey = "TemplatesToTeamsMigrationComplete" - UniqueIDsMigrationKey = "UniqueIDsMigrationComplete" - CategoryUUIDIDMigrationKey = "CategoryUuidIdMigrationComplete" - TeamLessBoardsMigrationKey = "TeamLessBoardsMigrationComplete" + // we group the inserts on batches of 1000 because PostgreSQL + // supports a limit of around 64K values (not rows) on an insert + // query, so we want to stay safely below. + CategoryInsertBatch = 1000 + + TemplatesToTeamsMigrationKey = "TemplatesToTeamsMigrationComplete" + UniqueIDsMigrationKey = "UniqueIDsMigrationComplete" + CategoryUUIDIDMigrationKey = "CategoryUuidIdMigrationComplete" + TeamLessBoardsMigrationKey = "TeamLessBoardsMigrationComplete" + DeletedMembershipBoardsMigrationKey = "DeletedMembershipBoardsMigrationComplete" ) func (s *SQLStore) getBlocksWithSameID(db sq.BaseRunner) ([]model.Block, error) { @@ -59,7 +65,7 @@ func (s *SQLStore) getBlocksWithSameID(db sq.BaseRunner) ([]model.Block, error) return s.blocksFromRows(rows) } -func (s *SQLStore) runUniqueIDsMigration() error { +func (s *SQLStore) RunUniqueIDsMigration() error { setting, err := s.GetSystemSetting(UniqueIDsMigrationKey) if err != nil { return fmt.Errorf("cannot get migration state: %w", err) @@ -122,7 +128,11 @@ func (s *SQLStore) runUniqueIDsMigration() error { return nil } -func (s *SQLStore) runCategoryUUIDIDMigration() error { +// RunCategoryUUIDIDMigration takes care of deriving the categories +// from the boards and its memberships. The name references UUID +// because of the preexisting purpose of this migration, and has been +// preserved for compatibility with already migrated instances. +func (s *SQLStore) RunCategoryUUIDIDMigration() error { setting, err := s.GetSystemSetting(CategoryUUIDIDMigrationKey) if err != nil { return fmt.Errorf("cannot get migration state: %w", err) @@ -140,159 +150,197 @@ func (s *SQLStore) runCategoryUUIDIDMigration() error { return txErr } - if err := s.updateCategoryIDs(tx); err != nil { - return err - } + if s.isPlugin { + if err := s.createCategories(tx); err != nil { + if rollbackErr := tx.Rollback(); rollbackErr != nil { + s.logger.Error("category UUIDs insert categories transaction rollback error", mlog.Err(rollbackErr), mlog.String("methodName", "setSystemSetting")) + } + return err + } - if err := s.updateCategoryBlocksIDs(tx); err != nil { - return err + if err := s.createCategoryBoards(tx); err != nil { + if rollbackErr := tx.Rollback(); rollbackErr != nil { + s.logger.Error("category UUIDs insert category boards transaction rollback error", mlog.Err(rollbackErr), mlog.String("methodName", "setSystemSetting")) + } + return err + } } if err := s.setSystemSetting(tx, CategoryUUIDIDMigrationKey, strconv.FormatBool(true)); err != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil { - s.logger.Error("category IDs transaction rollback error", mlog.Err(rollbackErr), mlog.String("methodName", "setSystemSetting")) + s.logger.Error("category UUIDs transaction rollback error", mlog.Err(rollbackErr), mlog.String("methodName", "setSystemSetting")) } return fmt.Errorf("cannot mark migration as completed: %w", err) } if err := tx.Commit(); err != nil { - return fmt.Errorf("cannot commit category IDs transaction: %w", err) + return fmt.Errorf("cannot commit category UUIDs transaction: %w", err) } - s.logger.Debug("category IDs migration finished successfully") + s.logger.Debug("category UUIDs migration finished successfully") return nil } -func (s *SQLStore) updateCategoryIDs(db sq.BaseRunner) error { - // fetch all category IDs - oldCategoryIDs, err := s.getIDs(db, "categories") - if err != nil { - return err - } - - // map old category ID to new ID - categoryIDs := map[string]string{} - for _, oldID := range oldCategoryIDs { - newID := utils.NewID(utils.IDTypeNone) - categoryIDs[oldID] = newID - } - - // update for each category ID. - // Update the new ID in category table, - // and update corresponding rows in category boards table. - for oldID, newID := range categoryIDs { - if err := s.updateCategoryID(db, oldID, newID); err != nil { - return err - } - } - - return nil -} - -func (s *SQLStore) getIDs(db sq.BaseRunner, table string) ([]string, error) { +func (s *SQLStore) createCategories(db sq.BaseRunner) error { rows, err := s.getQueryBuilder(db). - Select("id"). - From(s.tablePrefix + table). + Select("c.DisplayName, cm.UserId, c.TeamId, cm.ChannelId"). + From(s.tablePrefix + "boards boards"). + Join("ChannelMembers cm on boards.channel_id = cm.ChannelId"). + Join("Channels c on cm.ChannelId = c.id and (c.Type = 'O' or c.Type = 'P')"). + GroupBy("cm.UserId, c.TeamId, cm.ChannelId, c.DisplayName"). Query() if err != nil { - s.logger.Error("getIDs error", mlog.String("table", table), mlog.Err(err)) - return nil, err + s.logger.Error("get boards data error", mlog.Err(err)) + return err } - defer s.CloseRows(rows) - var categoryIDs []string + + initQuery := func() sq.InsertBuilder { + return s.getQueryBuilder(db). + Insert(s.tablePrefix+"categories"). + Columns( + "id", + "name", + "user_id", + "team_id", + "channel_id", + "create_at", + "update_at", + "delete_at", + ) + } + // query will accumulate the insert values until the limit is + // reached, and then it will be stored and reset + query := initQuery() + // queryList stores those queries that already reached the limit + // to be run when all the data is processed + queryList := []sq.InsertBuilder{} + counter := 0 + now := model.GetMillis() + for rows.Next() { - var id string - err := rows.Scan(&id) + var displayName string + var userID string + var teamID string + var channelID string + + err := rows.Scan( + &displayName, + &userID, + &teamID, + &channelID, + ) if err != nil { - s.logger.Error("getIDs scan row error", mlog.String("table", table), mlog.Err(err)) - return nil, err + return fmt.Errorf("cannot scan result while trying to create categories: %w", err) } - categoryIDs = append(categoryIDs, id) + query = query.Values( + utils.NewID(utils.IDTypeNone), + displayName, + userID, + teamID, + channelID, + now, + 0, + 0, + ) + + counter++ + if counter%CategoryInsertBatch == 0 { + queryList = append(queryList, query) + query = initQuery() + } } - return categoryIDs, nil -} - -func (s *SQLStore) updateCategoryID(db sq.BaseRunner, oldID, newID string) error { - // update in category table - rows, err := s.getQueryBuilder(db). - Update(s.tablePrefix+"categories"). - Set("id", newID). - Where(sq.Eq{"id": oldID}). - Query() - - if err != nil { - s.logger.Error("updateCategoryID update category error", mlog.Err(err)) - return err + if counter%CategoryInsertBatch != 0 { + queryList = append(queryList, query) } - if err = rows.Close(); err != nil { - s.logger.Error("updateCategoryID error closing rows after updating categories table IDs", mlog.Err(err)) - return err - } - - // update category boards table - - rows, err = s.getQueryBuilder(db). - Update(s.tablePrefix+"category_boards"). - Set("category_id", newID). - Where(sq.Eq{"category_id": oldID}). - Query() - - if err != nil { - s.logger.Error("updateCategoryID update category boards error", mlog.Err(err)) - return err - } - - if err := rows.Close(); err != nil { - s.logger.Error("updateCategoryID error closing rows after updating category boards table IDs", mlog.Err(err)) - return err + for _, q := range queryList { + if _, err := q.Exec(); err != nil { + return fmt.Errorf("cannot create category values: %w", err) + } } return nil } -func (s *SQLStore) updateCategoryBlocksIDs(db sq.BaseRunner) error { - // fetch all category IDs - oldCategoryIDs, err := s.getIDs(db, "category_boards") - if err != nil { - return err - } - - // map old category ID to new ID - categoryIDs := map[string]string{} - for _, oldID := range oldCategoryIDs { - newID := utils.NewID(utils.IDTypeNone) - categoryIDs[oldID] = newID - } - - // update for each category ID. - // Update the new ID in category table, - // and update corresponding rows in category boards table. - for oldID, newID := range categoryIDs { - if err := s.updateCategoryBlocksID(db, oldID, newID); err != nil { - return err - } - } - return nil -} - -func (s *SQLStore) updateCategoryBlocksID(db sq.BaseRunner, oldID, newID string) error { - // update in category table +func (s *SQLStore) createCategoryBoards(db sq.BaseRunner) error { rows, err := s.getQueryBuilder(db). - Update(s.tablePrefix+"category_boards"). - Set("id", newID). - Where(sq.Eq{"id": oldID}). + Select("categories.user_id, categories.id, boards.id"). + From(s.tablePrefix + "categories categories"). + Join(s.tablePrefix + "boards boards on categories.channel_id = boards.channel_id AND boards.is_template = false"). Query() if err != nil { - s.logger.Error("updateCategoryBlocksID update category error", mlog.Err(err)) + s.logger.Error("get categories data error", mlog.Err(err)) return err } - rows.Close() + defer s.CloseRows(rows) + + initQuery := func() sq.InsertBuilder { + return s.getQueryBuilder(db). + Insert(s.tablePrefix+"category_boards"). + Columns( + "id", + "user_id", + "category_id", + "board_id", + "create_at", + "update_at", + "delete_at", + ) + } + // query will accumulate the insert values until the limit is + // reached, and then it will be stored and reset + query := initQuery() + // queryList stores those queries that already reached the limit + // to be run when all the data is processed + queryList := []sq.InsertBuilder{} + counter := 0 + now := model.GetMillis() + + for rows.Next() { + var userID string + var categoryID string + var boardID string + + err := rows.Scan( + &userID, + &categoryID, + &boardID, + ) + if err != nil { + return fmt.Errorf("cannot scan result while trying to create category boards: %w", err) + } + + query = query.Values( + utils.NewID(utils.IDTypeNone), + userID, + categoryID, + boardID, + now, + 0, + 0, + ) + + counter++ + if counter%CategoryInsertBatch == 0 { + queryList = append(queryList, query) + query = initQuery() + } + } + + if counter%CategoryInsertBatch != 0 { + queryList = append(queryList, query) + } + + for _, q := range queryList { + if _, err := q.Exec(); err != nil { + return fmt.Errorf("cannot create category boards values: %w", err) + } + } return nil } @@ -300,7 +348,7 @@ func (s *SQLStore) updateCategoryBlocksID(db sq.BaseRunner, oldID, newID string) // We no longer support boards existing in DMs and private // group messages. This function migrates all boards // belonging to a DM to the best possible team. -func (s *SQLStore) migrateTeamLessBoards() error { +func (s *SQLStore) RunTeamLessBoardsMigration() error { if !s.isPlugin { return nil } @@ -329,7 +377,7 @@ func (s *SQLStore) migrateTeamLessBoards() error { tx, err := s.db.BeginTx(context.Background(), nil) if err != nil { - s.logger.Error("error starting transaction in migrateTeamLessBoards", mlog.Err(err)) + s.logger.Error("error starting transaction in runTeamLessBoardsMigration", mlog.Err(err)) return err } @@ -343,6 +391,7 @@ func (s *SQLStore) migrateTeamLessBoards() error { if err != nil { // don't let one board's error spoil // the mood for others + s.logger.Error("could not find the best team for board during team less boards migration. Continuing", mlog.String("boardID", boards[i].ID)) continue } } @@ -364,13 +413,13 @@ func (s *SQLStore) migrateTeamLessBoards() error { if err := s.setSystemSetting(tx, TeamLessBoardsMigrationKey, strconv.FormatBool(true)); err != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil { - s.logger.Error("transaction rollback error", mlog.Err(rollbackErr), mlog.String("methodName", "migrateTeamLessBoards")) + s.logger.Error("transaction rollback error", mlog.Err(rollbackErr), mlog.String("methodName", "runTeamLessBoardsMigration")) } return fmt.Errorf("cannot mark migration as completed: %w", err) } if err := tx.Commit(); err != nil { - s.logger.Error("failed to commit migrateTeamLessBoards transaction", mlog.Err(err)) + s.logger.Error("failed to commit runTeamLessBoardsMigration transaction", mlog.Err(err)) return err } @@ -467,10 +516,15 @@ func (s *SQLStore) getBestTeamForBoard(tx sq.BaseRunner, board *model.Board) (st func (s *SQLStore) getBoardUserTeams(tx sq.BaseRunner, board *model.Board) (map[string][]string, error) { query := s.getQueryBuilder(tx). - Select("TeamMembers.UserId", "TeamMembers.TeamId"). - From("ChannelMembers"). - Join("TeamMembers ON ChannelMembers.UserId = TeamMembers.UserId"). - Where(sq.Eq{"ChannelId": board.ChannelID}) + Select("tm.UserId", "tm.TeamId"). + From("ChannelMembers cm"). + Join("TeamMembers tm ON cm.UserId = tm.UserId"). + Join("Teams t ON tm.TeamId = t.Id"). + Where(sq.Eq{ + "cm.ChannelId": board.ChannelID, + "t.DeleteAt": 0, + "tm.DeleteAt": 0, + }) rows, err := query.Query() if err != nil { @@ -495,3 +549,99 @@ func (s *SQLStore) getBoardUserTeams(tx sq.BaseRunner, board *model.Board) (map[ return userTeams, nil } + +func (s *SQLStore) RunDeletedMembershipBoardsMigration() error { + if !s.isPlugin { + return nil + } + + setting, err := s.GetSystemSetting(DeletedMembershipBoardsMigrationKey) + if err != nil { + return fmt.Errorf("cannot get deleted membership boards migration state: %w", err) + } + + // If the migration is already completed, do not run it again. + if hasAlreadyRun, _ := strconv.ParseBool(setting); hasAlreadyRun { + return nil + } + + boards, err := s.getDeletedMembershipBoards(s.db) + if err != nil { + return err + } + + if len(boards) == 0 { + s.logger.Debug("No boards with owner not anymore on their team found, marking runDeletedMembershipBoardsMigration as done") + if sErr := s.SetSystemSetting(DeletedMembershipBoardsMigrationKey, strconv.FormatBool(true)); sErr != nil { + return fmt.Errorf("cannot mark migration as completed: %w", sErr) + } + return nil + } + + s.logger.Debug("Migrating boards with owner not anymore on their team", mlog.Int("count", len(boards))) + + tx, err := s.db.BeginTx(context.Background(), nil) + if err != nil { + s.logger.Error("error starting transaction in runDeletedMembershipBoardsMigration", mlog.Err(err)) + return err + } + + for i := range boards { + teamID, err := s.getBestTeamForBoard(s.db, boards[i]) + if err != nil { + // don't let one board's error spoil + // the mood for others + s.logger.Error("could not find the best team for board during deleted membership boards migration. Continuing", mlog.String("boardID", boards[i].ID)) + continue + } + + boards[i].TeamID = teamID + + query := s.getQueryBuilder(tx). + Update(s.tablePrefix+"boards"). + Set("team_id", teamID). + Where(sq.Eq{"id": boards[i].ID}) + + if _, err := query.Exec(); err != nil { + s.logger.Error("failed to set team id for board", mlog.String("board_id", boards[i].ID), mlog.String("team_id", teamID), mlog.Err(err)) + return err + } + } + + if err := s.setSystemSetting(tx, DeletedMembershipBoardsMigrationKey, strconv.FormatBool(true)); err != nil { + if rollbackErr := tx.Rollback(); rollbackErr != nil { + s.logger.Error("transaction rollback error", mlog.Err(rollbackErr), mlog.String("methodName", "runDeletedMembershipBoardsMigration")) + } + return fmt.Errorf("cannot mark migration as completed: %w", err) + } + + if err := tx.Commit(); err != nil { + s.logger.Error("failed to commit runDeletedMembershipBoardsMigration transaction", mlog.Err(err)) + return err + } + + return nil +} + +// getDeletedMembershipBoards retrieves those boards whose creator is +// associated to the board's team with a deleted team membership. +func (s *SQLStore) getDeletedMembershipBoards(tx sq.BaseRunner) ([]*model.Board, error) { + rows, err := s.getQueryBuilder(tx). + Select(legacyBoardFields("b.")...). + From(s.tablePrefix + "boards b"). + Join("TeamMembers tm ON b.created_by = tm.UserId"). + Where("b.team_id = tm.TeamId"). + Where(sq.NotEq{"tm.DeleteAt": 0}). + Query() + if err != nil { + return nil, err + } + defer s.CloseRows(rows) + + boards, err := s.boardsFromRows(rows) + if err != nil { + return nil, err + } + + return boards, err +} diff --git a/server/services/store/sqlstore/data_migrations_test.go b/server/services/store/sqlstore/data_migrations_test.go index 0562313bf..1d157e31a 100644 --- a/server/services/store/sqlstore/data_migrations_test.go +++ b/server/services/store/sqlstore/data_migrations_test.go @@ -194,7 +194,7 @@ func TestRunUniqueIDsMigration(t *testing.T) { time.Sleep(100 * time.Millisecond) } - err := sqlStore.runUniqueIDsMigration() + err := sqlStore.RunUniqueIDsMigration() require.NoError(t, err) // blocks from workspace 1 haven't changed, so we can simply fetch them diff --git a/server/services/store/sqlstore/helpers_test.go b/server/services/store/sqlstore/helpers_test.go index 3916be364..d05eba944 100644 --- a/server/services/store/sqlstore/helpers_test.go +++ b/server/services/store/sqlstore/helpers_test.go @@ -31,7 +31,7 @@ func SetupTests(t *testing.T) (store.Store, func()) { IsPlugin: false, } store, err := New(storeParams) - require.Nil(t, err) + require.NoError(t, err) tearDown := func() { defer func() { _ = logger.Shutdown() }() diff --git a/server/services/store/sqlstore/legacy_blocks.go b/server/services/store/sqlstore/legacy_blocks.go index 89132f35f..c27ba002d 100644 --- a/server/services/store/sqlstore/legacy_blocks.go +++ b/server/services/store/sqlstore/legacy_blocks.go @@ -44,9 +44,12 @@ func legacyBoardFields(prefix string) []string { prefixedFields := make([]string, len(fields)) for i, field := range fields { - if strings.HasPrefix(field, "COALESCE(") { + switch { + case strings.HasPrefix(field, "COALESCE("): prefixedFields[i] = strings.Replace(field, "COALESCE(", "COALESCE("+prefix, 1) - } else { + case field == "''": + prefixedFields[i] = field + default: prefixedFields[i] = prefix + field } } diff --git a/server/services/store/sqlstore/migrate.go b/server/services/store/sqlstore/migrate.go index 8afc809b2..8373348b7 100644 --- a/server/services/store/sqlstore/migrate.go +++ b/server/services/store/sqlstore/migrate.go @@ -13,6 +13,7 @@ import ( "github.com/mattermost/morph/models" "github.com/mattermost/mattermost-server/v6/shared/mlog" + "github.com/mattermost/mattermost-server/v6/store/sqlstore" "github.com/mattermost/morph" drivers "github.com/mattermost/morph/drivers" @@ -21,7 +22,6 @@ import ( sqlite "github.com/mattermost/morph/drivers/sqlite" embedded "github.com/mattermost/morph/sources/embedded" - mysqldriver "github.com/go-sql-driver/mysql" _ "github.com/lib/pq" // postgres driver sq "github.com/Masterminds/squirrel" @@ -31,7 +31,7 @@ import ( ) //go:embed migrations -var assets embed.FS +var Assets embed.FS const ( uniqueIDsMigrationRequiredVersion = 14 @@ -43,30 +43,6 @@ const ( var errChannelCreatorNotInTeam = errors.New("channel creator not found in user teams") -func appendMultipleStatementsFlag(connectionString string) (string, error) { - config, err := mysqldriver.ParseDSN(connectionString) - if err != nil { - return "", err - } - - if config.Params == nil { - config.Params = map[string]string{} - } - - config.Params["multiStatements"] = "true" - return config.FormatDSN(), nil -} - -// resetReadTimeout removes the timeout contraint from the MySQL dsn. -func resetReadTimeout(dataSource string) (string, error) { - config, err := mysqldriver.ParseDSN(dataSource) - if err != nil { - return "", err - } - config.ReadTimeout = 0 - return config.FormatDSN(), nil -} - // migrations in MySQL need to run with the multiStatements flag // enabled, so this method creates a new connection ensuring that it's // enabled. @@ -74,12 +50,12 @@ func (s *SQLStore) getMigrationConnection() (*sql.DB, error) { connectionString := s.connectionString if s.dbType == model.MysqlDBType { var err error - connectionString, err = resetReadTimeout(connectionString) + connectionString, err = sqlstore.ResetReadTimeout(connectionString) if err != nil { return nil, err } - connectionString, err = appendMultipleStatementsFlag(connectionString) + connectionString, err = sqlstore.AppendMultipleStatementsFlag(connectionString) if err != nil { return nil, err } @@ -141,7 +117,7 @@ func (s *SQLStore) Migrate() error { } } - assetsList, err := assets.ReadDir("migrations") + assetsList, err := Assets.ReadDir("migrations") if err != nil { return err } @@ -162,7 +138,7 @@ func (s *SQLStore) Migrate() error { migrationAssets := &embedded.AssetSource{ Names: assetNamesForDriver, AssetFunc: func(name string) ([]byte, error) { - asset, mErr := assets.ReadFile("migrations/" + name) + asset, mErr := Assets.ReadFile("migrations/" + name) if mErr != nil { return nil, mErr } @@ -229,7 +205,7 @@ func (s *SQLStore) Migrate() error { return mErr } - if mErr := s.runUniqueIDsMigration(); mErr != nil { + if mErr := s.RunUniqueIDsMigration(); mErr != nil { return fmt.Errorf("error running unique IDs migration: %w", mErr) } @@ -237,7 +213,11 @@ func (s *SQLStore) Migrate() error { return mErr } - if mErr := s.migrateTeamLessBoards(); mErr != nil { + if mErr := s.RunTeamLessBoardsMigration(); mErr != nil { + return mErr + } + + if mErr := s.RunDeletedMembershipBoardsMigration(); mErr != nil { return mErr } @@ -245,7 +225,7 @@ func (s *SQLStore) Migrate() error { return mErr } - if mErr := s.runCategoryUUIDIDMigration(); mErr != nil { + if mErr := s.RunCategoryUUIDIDMigration(); mErr != nil { return fmt.Errorf("error running categoryID migration: %w", mErr) } diff --git a/server/services/store/sqlstore/migrate_test.go b/server/services/store/sqlstore/migrate_test.go deleted file mode 100644 index 12ba070a6..000000000 --- a/server/services/store/sqlstore/migrate_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package sqlstore - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestGetMySQLMigrationConnection(t *testing.T) { - testCases := []struct { - Scenario string - DSN string - ExpectedDSN string - }{ - { - "Should append multiStatements param to the DSN path with existing params", - "user:rand?&ompasswith@character@unix(/var/run/mysqld/mysqld.sock)/focalboard?writeTimeout=30s", - "user:rand?&ompasswith@character@unix(/var/run/mysqld/mysqld.sock)/focalboard?writeTimeout=30s&multiStatements=true", - }, - { - "Should append multiStatements param to the DSN path with no existing params", - "user:rand?&ompasswith@character@unix(/var/run/mysqld/mysqld.sock)/focalboard", - "user:rand?&ompasswith@character@unix(/var/run/mysqld/mysqld.sock)/focalboard?multiStatements=true", - }, - } - - for _, tc := range testCases { - t.Run(tc.Scenario, func(t *testing.T) { - res, err := appendMultipleStatementsFlag(tc.DSN) - require.NoError(t, err) - assert.Equal(t, tc.ExpectedDSN, res) - }) - } -} diff --git a/server/services/store/sqlstore/migrations/000018_add_teams_and_boards.up.sql b/server/services/store/sqlstore/migrations/000018_add_teams_and_boards.up.sql index bc582ae02..ba1454d19 100644 --- a/server/services/store/sqlstore/migrations/000018_add_teams_and_boards.up.sql +++ b/server/services/store/sqlstore/migrations/000018_add_teams_and_boards.up.sql @@ -302,7 +302,7 @@ INSERT INTO {{.prefix}}board_members ( SELECT B.Id, CM.UserId, CM.Roles, TRUE, TRUE, FALSE, FALSE FROM {{.prefix}}boards AS B INNER JOIN ChannelMembers as CM ON CM.ChannelId=B.channel_id - WHERE CM.SchemeAdmin=True + WHERE CM.SchemeAdmin=True OR (CM.UserId=B.created_by) ); {{end}} diff --git a/server/services/store/sqlstore/migrations/000019_populate_categories.up.sql b/server/services/store/sqlstore/migrations/000019_populate_categories.up.sql index 695402d60..d551eeb39 100644 --- a/server/services/store/sqlstore/migrations/000019_populate_categories.up.sql +++ b/server/services/store/sqlstore/migrations/000019_populate_categories.up.sql @@ -11,36 +11,3 @@ CREATE TABLE IF NOT EXISTS {{.prefix}}categories ( ) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}}; CREATE INDEX idx_categories_user_id_team_id ON {{.prefix}}categories(user_id, team_id); - -{{if .plugin}} - INSERT INTO {{.prefix}}categories( - id, - name, - user_id, - team_id, - channel_id, - create_at, - update_at, - delete_at - ) - SELECT - {{ if .postgres }} - REPLACE(uuid_in(md5(random()::text || clock_timestamp()::text)::cstring)::varchar, '-', ''), - {{ end }} - {{ if .mysql }} - UUID(), - {{ end }} - c.DisplayName, - cm.UserId, - c.TeamId, - cm.ChannelId, - {{if .postgres}}(extract(epoch from now())*1000)::bigint,{{end}} - {{if .mysql}}UNIX_TIMESTAMP() * 1000,{{end}} - 0, - 0 - FROM - {{.prefix}}boards boards - JOIN ChannelMembers cm on boards.channel_id = cm.ChannelId - JOIN Channels c on cm.ChannelId = c.id and (c.Type = 'O' or c.Type = 'P') - GROUP BY cm.UserId, c.TeamId, cm.ChannelId, c.DisplayName; -{{end}} diff --git a/server/services/store/sqlstore/migrations/000020_populate_category_blocks.up.sql b/server/services/store/sqlstore/migrations/000020_populate_category_blocks.up.sql index 2846c53eb..c5dd300c7 100644 --- a/server/services/store/sqlstore/migrations/000020_populate_category_blocks.up.sql +++ b/server/services/store/sqlstore/migrations/000020_populate_category_blocks.up.sql @@ -7,28 +7,6 @@ CREATE TABLE IF NOT EXISTS {{.prefix}}category_boards ( update_at BIGINT, delete_at BIGINT, PRIMARY KEY (id) - ) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}}; +) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}}; CREATE INDEX idx_categoryboards_category_id ON {{.prefix}}category_boards(category_id); - -{{if .plugin}} - INSERT INTO {{.prefix}}category_boards(id, user_id, category_id, board_id, create_at, update_at, delete_at) - SELECT - {{ if .postgres }} - REPLACE(uuid_in(md5(random()::text || clock_timestamp()::text)::cstring)::varchar, '-', ''), - {{ end }} - {{ if .mysql }} - UUID(), - {{ end }} - {{.prefix}}categories.user_id, - {{.prefix}}categories.id, - {{.prefix}}boards.id, - {{if .postgres}}(extract(epoch from now())*1000)::bigint,{{end}} - {{if .mysql}}UNIX_TIMESTAMP() * 1000,{{end}} - 0, - 0 - FROM - {{.prefix}}categories - JOIN {{.prefix}}boards ON {{.prefix}}categories.channel_id = {{.prefix}}boards.channel_id - AND {{.prefix}}boards.is_template = false; -{{end}} diff --git a/server/services/store/sqlstore/migrations/000023_persist_category_collapsed_state.down.sql b/server/services/store/sqlstore/migrations/000023_persist_category_collapsed_state.down.sql new file mode 100644 index 000000000..ccf963529 --- /dev/null +++ b/server/services/store/sqlstore/migrations/000023_persist_category_collapsed_state.down.sql @@ -0,0 +1 @@ +ALTER TABLE {{.prefix}}categories DROP COLUMN collapsed; diff --git a/server/services/store/sqlstore/migrations/000023_persist_category_collapsed_state.up.sql b/server/services/store/sqlstore/migrations/000023_persist_category_collapsed_state.up.sql new file mode 100644 index 000000000..2fd8eefb0 --- /dev/null +++ b/server/services/store/sqlstore/migrations/000023_persist_category_collapsed_state.up.sql @@ -0,0 +1 @@ +ALTER TABLE {{.prefix}}categories ADD collapsed boolean default false; diff --git a/server/services/store/sqlstore/migrations/000024_mark_existsing_categories_collapsed.down.sql b/server/services/store/sqlstore/migrations/000024_mark_existsing_categories_collapsed.down.sql new file mode 100644 index 000000000..920aab237 --- /dev/null +++ b/server/services/store/sqlstore/migrations/000024_mark_existsing_categories_collapsed.down.sql @@ -0,0 +1 @@ +UPDATE {{.prefix}}categories SET collapsed = false; diff --git a/server/services/store/sqlstore/migrations/000024_mark_existsing_categories_collapsed.up.sql b/server/services/store/sqlstore/migrations/000024_mark_existsing_categories_collapsed.up.sql new file mode 100644 index 000000000..a5b5ad9bc --- /dev/null +++ b/server/services/store/sqlstore/migrations/000024_mark_existsing_categories_collapsed.up.sql @@ -0,0 +1 @@ +UPDATE {{.prefix}}categories SET collapsed = true; diff --git a/server/services/store/sqlstore/migrationstests/boards_migrator_test.go b/server/services/store/sqlstore/migrationstests/boards_migrator_test.go new file mode 100644 index 000000000..7427c118a --- /dev/null +++ b/server/services/store/sqlstore/migrationstests/boards_migrator_test.go @@ -0,0 +1,266 @@ +package migrationstests + +import ( + "bytes" + "context" + "database/sql" + "fmt" + "path/filepath" + "text/template" + + "github.com/mattermost/mattermost-plugin-api/cluster" + "github.com/mattermost/morph" + "github.com/mattermost/morph/drivers" + "github.com/mattermost/morph/drivers/mysql" + "github.com/mattermost/morph/drivers/postgres" + embedded "github.com/mattermost/morph/sources/embedded" + "github.com/mgdelacroix/foundation" + + "github.com/mattermost/mattermost-server/v6/db" + "github.com/mattermost/mattermost-server/v6/shared/mlog" + mmSqlStore "github.com/mattermost/mattermost-server/v6/store/sqlstore" + + "github.com/mattermost/focalboard/server/model" + "github.com/mattermost/focalboard/server/services/store/sqlstore" +) + +var tablePrefix = "focalboard_" + +type BoardsMigrator struct { + withMattermostMigrations bool + connString string + driverName string + db *sql.DB + store *sqlstore.SQLStore + morphEngine *morph.Morph + morphDriver drivers.Driver +} + +func NewBoardsMigrator(withMattermostMigrations bool) *BoardsMigrator { + return &BoardsMigrator{ + withMattermostMigrations: withMattermostMigrations, + } +} + +func (bm *BoardsMigrator) runMattermostMigrations() error { + assets := db.Assets() + assetsList, err := assets.ReadDir(filepath.Join("migrations", bm.driverName)) + if err != nil { + return err + } + + assetNames := make([]string, len(assetsList)) + for i, entry := range assetsList { + assetNames[i] = entry.Name() + } + + src, err := embedded.WithInstance(&embedded.AssetSource{ + Names: assetNames, + AssetFunc: func(name string) ([]byte, error) { + return assets.ReadFile(filepath.Join("migrations", bm.driverName, name)) + }, + }) + if err != nil { + return err + } + + driver, err := bm.getDriver("") + if err != nil { + return err + } + + engine, err := morph.New(context.Background(), driver, src) + if err != nil { + return err + } + defer engine.Close() + + return engine.ApplyAll() +} + +func (bm *BoardsMigrator) getDriver(migrationsTable string) (drivers.Driver, error) { + migrationConfig := drivers.Config{ + StatementTimeoutInSecs: 1000000, + MigrationsTable: migrationsTable, + } + + var driver drivers.Driver + var err error + if bm.driverName == model.PostgresDBType { + driver, err = postgres.WithInstance(bm.db, &postgres.Config{Config: migrationConfig}) + if err != nil { + return nil, err + } + } + + if bm.driverName == model.MysqlDBType { + driver, err = mysql.WithInstance(bm.db, &mysql.Config{Config: migrationConfig}) + if err != nil { + return nil, err + } + } + + return driver, nil +} + +func (bm *BoardsMigrator) getMorphConnection() (*morph.Morph, drivers.Driver, error) { + driver, err := bm.getDriver(fmt.Sprintf("%sschema_migrations", tablePrefix)) + if err != nil { + return nil, nil, err + } + + assetsList, err := sqlstore.Assets.ReadDir("migrations") + if err != nil { + return nil, nil, err + } + assetNamesForDriver := make([]string, len(assetsList)) + for i, dirEntry := range assetsList { + assetNamesForDriver[i] = dirEntry.Name() + } + + params := map[string]interface{}{ + "prefix": tablePrefix, + "postgres": bm.driverName == model.PostgresDBType, + "sqlite": bm.driverName == model.SqliteDBType, + "mysql": bm.driverName == model.MysqlDBType, + "plugin": bm.withMattermostMigrations, + "singleUser": false, + } + + migrationAssets := &embedded.AssetSource{ + Names: assetNamesForDriver, + AssetFunc: func(name string) ([]byte, error) { + asset, mErr := sqlstore.Assets.ReadFile("migrations/" + name) + if mErr != nil { + return nil, mErr + } + + tmpl, pErr := template.New("sql").Parse(string(asset)) + if pErr != nil { + return nil, pErr + } + buffer := bytes.NewBufferString("") + + err = tmpl.Execute(buffer, params) + if err != nil { + return nil, err + } + + return buffer.Bytes(), nil + }, + } + + src, err := embedded.WithInstance(migrationAssets) + if err != nil { + return nil, nil, err + } + + engine, err := morph.New(context.Background(), driver, src) + if err != nil { + return nil, nil, err + } + + return engine, driver, nil +} + +func (bm *BoardsMigrator) Setup() error { + var err error + bm.driverName, bm.connString, err = sqlstore.PrepareNewTestDatabase() + if err != nil { + return err + } + + if bm.driverName == model.MysqlDBType { + bm.connString, err = mmSqlStore.ResetReadTimeout(bm.connString) + if err != nil { + return err + } + + bm.connString, err = mmSqlStore.AppendMultipleStatementsFlag(bm.connString) + if err != nil { + return err + } + } + + var dbErr error + bm.db, dbErr = sql.Open(bm.driverName, bm.connString) + if dbErr != nil { + return dbErr + } + + if err := bm.db.Ping(); err != nil { + return err + } + + if bm.withMattermostMigrations { + if err := bm.runMattermostMigrations(); err != nil { + return err + } + } + + storeParams := sqlstore.Params{ + DBType: bm.driverName, + ConnectionString: bm.connString, + TablePrefix: tablePrefix, + Logger: mlog.CreateConsoleTestLogger(false, mlog.LvlDebug), + DB: bm.db, + IsPlugin: true, + NewMutexFn: func(name string) (*cluster.Mutex, error) { + return nil, fmt.Errorf("not implemented") + }, + SkipMigrations: true, + } + bm.store, err = sqlstore.New(storeParams) + if err != nil { + return err + } + + morphEngine, morphDriver, err := bm.getMorphConnection() + if err != nil { + return err + } + bm.morphEngine = morphEngine + bm.morphDriver = morphDriver + + return nil +} + +func (bm *BoardsMigrator) MigrateToStep(step int) error { + applied, err := bm.morphDriver.AppliedMigrations() + if err != nil { + return err + } + currentVersion := len(applied) + + if _, err := bm.morphEngine.Apply(step - currentVersion); err != nil { + return err + } + + return nil +} + +func (bm *BoardsMigrator) Interceptors() map[int]foundation.Interceptor { + return map[int]foundation.Interceptor{ + 18: bm.store.RunDeletedMembershipBoardsMigration, + } +} + +func (bm *BoardsMigrator) TearDown() error { + if err := bm.morphEngine.Close(); err != nil { + return err + } + + if err := bm.db.Close(); err != nil { + return err + } + + return nil +} + +func (bm *BoardsMigrator) DriverName() string { + return bm.driverName +} + +func (bm *BoardsMigrator) DB() *sql.DB { + return bm.db +} diff --git a/server/services/store/sqlstore/migrationstests/deleted_membership_boards_migration_test.go b/server/services/store/sqlstore/migrationstests/deleted_membership_boards_migration_test.go new file mode 100644 index 000000000..563427134 --- /dev/null +++ b/server/services/store/sqlstore/migrationstests/deleted_membership_boards_migration_test.go @@ -0,0 +1,44 @@ +package migrationstests + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDeletedMembershipBoardsMigration(t *testing.T) { + t.Run("should detect a board linked to a team in which the owner has a deleted membership and restore it", func(t *testing.T) { + th, tearDown := SetupPluginTestHelper(t) + defer tearDown() + + th.f.MigrateToStepSkippingLastInterceptor(18). + ExecFile("./fixtures/deletedMembershipBoardsMigrationFixtures.sql") + + boardGroupChannel := struct { + Created_By string + Team_ID string + }{} + boardDirectMessage := struct { + Created_By string + Team_ID string + }{} + + th.f.DB().Get(&boardGroupChannel, "SELECT created_by, team_id FROM focalboard_boards WHERE id = 'board-group-channel'") + require.Equal(t, "user-one", boardGroupChannel.Created_By) + require.Equal(t, "team-one", boardGroupChannel.Team_ID) + + th.f.DB().Get(&boardDirectMessage, "SELECT created_by, team_id FROM focalboard_boards WHERE id = 'board-group-channel'") + require.Equal(t, "user-one", boardDirectMessage.Created_By) + require.Equal(t, "team-one", boardDirectMessage.Team_ID) + + th.f.RunInterceptor(18) + + th.f.DB().Get(&boardGroupChannel, "SELECT created_by, team_id FROM focalboard_boards WHERE id = 'board-group-channel'") + require.Equal(t, "user-one", boardGroupChannel.Created_By) + require.Equal(t, "team-three", boardGroupChannel.Team_ID) + + th.f.DB().Get(&boardDirectMessage, "SELECT created_by, team_id FROM focalboard_boards WHERE id = 'board-group-channel'") + require.Equal(t, "user-one", boardDirectMessage.Created_By) + require.Equal(t, "team-three", boardDirectMessage.Team_ID) + }) +} diff --git a/server/services/store/sqlstore/migrationstests/fixtures/deletedMembershipBoardsMigrationFixtures.sql b/server/services/store/sqlstore/migrationstests/fixtures/deletedMembershipBoardsMigrationFixtures.sql new file mode 100644 index 000000000..a9f2b89bc --- /dev/null +++ b/server/services/store/sqlstore/migrationstests/fixtures/deletedMembershipBoardsMigrationFixtures.sql @@ -0,0 +1,47 @@ +INSERT INTO Teams +(Id, Name, Type, DeleteAt) +VALUES +('team-one', 'team-one', 'O', 0), +('team-two', 'team-two', 'O', 0), +('team-three', 'team-three', 'O', 0); + +INSERT INTO Channels +(Id, DeleteAt, TeamId, Type, Name, CreatorId) +VALUES +('group-channel', 0, 'team-one', 'G', 'group-channel', 'user-one'), +('direct-channel', 0, 'team-one', 'D', 'direct-channel', 'user-one'); + +INSERT INTO Users +(Id, Username, Email) +VALUES +('user-one', 'john-doe', 'john-doe@sample.com'), +('user-two', 'jane-doe', 'jane-doe@sample.com'); + +INSERT INTO focalboard_boards +(id, team_id, channel_id, created_by, modified_by, type, title, description, icon, show_description, is_template, create_at, update_at, delete_at) +VALUES +('board-group-channel', 'team-one', 'group-channel', 'user-one', 'user-one', 'P', 'Group Channel Board', '', '', false, false, 123, 123, 0), +('board-direct-channel', 'team-one', 'direct-channel', 'user-one', 'user-one', 'P', 'Direct Channel Board', '', '', false, false, 123, 123, 0); + +INSERT INTO focalboard_board_members +(board_id, user_id, scheme_admin) +VALUES +('board-group-channel', 'user-one', true), +('board-direct-channel', 'user-one', true); + +INSERT INTO TeamMembers +(TeamId, UserId, DeleteAt, SchemeAdmin) +VALUES +('team-one', 'user-one', 123, true), +('team-one', 'user-two', 123, true), +('team-two', 'user-one', 123, true), +('team-two', 'user-two', 123, true), +('team-three', 'user-one', 0, true), +('team-three', 'user-two', 0, true); + +INSERT INTO ChannelMembers +(ChannelId, UserId, SchemeUser, SchemeAdmin) +VALUES +('group-channel', 'user-one', true, true), +('group-channel', 'two-one', true, false), +('direct-channel', 'user-one', true, true); diff --git a/server/services/store/sqlstore/migrationstests/fixtures/test18AddTeamsAndBoardsSQLMigrationFixtures.sql b/server/services/store/sqlstore/migrationstests/fixtures/test18AddTeamsAndBoardsSQLMigrationFixtures.sql new file mode 100644 index 000000000..a3a14a071 --- /dev/null +++ b/server/services/store/sqlstore/migrationstests/fixtures/test18AddTeamsAndBoardsSQLMigrationFixtures.sql @@ -0,0 +1,7 @@ +INSERT INTO Channels (Id, CreateAt, UpdateAt, DeleteAt, TeamId, Type, Name, CreatorId) VALUES ('chan-id', 123, 123, 0, 'team-id', 'O', 'channel', 'user-id'); + +INSERT INTO focalboard_blocks +(id, workspace_id, root_id, parent_id, created_by, modified_by, type, title, create_at, update_at, delete_at) +VALUES +('board-id', 'chan-id', 'board-id', 'board-id', 'user-id', 'user-id', 'board', 'My Board', 123, 123, 0), +('card-id', 'chan-id', 'board-id', 'board-id', 'user-id', 'user-id', 'card', 'A card', 123, 123, 0); diff --git a/server/services/store/sqlstore/migrationstests/helpers_test.go b/server/services/store/sqlstore/migrationstests/helpers_test.go new file mode 100644 index 000000000..673ff3bff --- /dev/null +++ b/server/services/store/sqlstore/migrationstests/helpers_test.go @@ -0,0 +1,45 @@ +package migrationstests + +import ( + "os" + "strings" + "testing" + + "github.com/mattermost/focalboard/server/model" + "github.com/mgdelacroix/foundation" +) + +type TestHelper struct { + t *testing.T + f *foundation.Foundation + isPlugin bool +} + +func SetupPluginTestHelper(t *testing.T) (*TestHelper, func()) { + dbType := strings.TrimSpace(os.Getenv("FOCALBOARD_STORE_TEST_DB_TYPE")) + if dbType == "" || dbType == model.SqliteDBType { + t.Skip("Skipping plugin mode test for SQLite") + } + + return setupTestHelper(t, true) +} + +func SetupTestHelper(t *testing.T) (*TestHelper, func()) { + return setupTestHelper(t, false) +} + +func setupTestHelper(t *testing.T, isPlugin bool) (*TestHelper, func()) { + f := foundation.New(t, NewBoardsMigrator(isPlugin)) + + th := &TestHelper{ + t: t, + f: f, + isPlugin: isPlugin, + } + + tearDown := func() { + th.f.TearDown() + } + + return th, tearDown +} diff --git a/server/services/store/sqlstore/migrationstests/migration_18_test.go b/server/services/store/sqlstore/migrationstests/migration_18_test.go new file mode 100644 index 000000000..d29719eaa --- /dev/null +++ b/server/services/store/sqlstore/migrationstests/migration_18_test.go @@ -0,0 +1,54 @@ +package migrationstests + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test18AddTeamsAndBoardsSQLMigration(t *testing.T) { + t.Run("should migrate a block of type board to the boards table", func(t *testing.T) { + th, tearDown := SetupPluginTestHelper(t) + defer tearDown() + + th.f.MigrateToStep(17). + ExecFile("./fixtures/test18AddTeamsAndBoardsSQLMigrationFixtures.sql") + + board := struct { + ID string + Title string + Type string + }{} + + // we check first that the board is inside the blocks table as + // a block of board type + err := th.f.DB().Get(&board, "SELECT id, title, type FROM focalboard_blocks WHERE id = 'board-id'") + require.NoError(t, err) + require.Equal(t, "My Board", board.Title) + require.Equal(t, "board", board.Type) + + // then we run the migration + th.f.MigrateToStep(18) + + // we assert that the board is now a block + bErr := th.f.DB().Get(&board, "SELECT id, title, type FROM focalboard_boards WHERE id = 'board-id'") + require.NoError(t, bErr) + require.Equal(t, "My Board", board.Title) + require.Equal(t, "O", board.Type) + + card := struct { + Title string + Type string + Parent_ID string + Board_ID string + }{} + + // we fetch the card to ensure that the + cErr := th.f.DB().Get(&card, "SELECT title, type, parent_id, board_id FROM focalboard_blocks WHERE id = 'card-id'") + require.NoError(t, cErr) + require.Equal(t, "A card", card.Title) + require.Equal(t, "card", card.Type) + require.Equal(t, board.ID, card.Parent_ID) + require.Equal(t, board.ID, card.Board_ID) + }) +} diff --git a/server/services/store/sqlstore/params.go b/server/services/store/sqlstore/params.go index f32981055..17e1689b7 100644 --- a/server/services/store/sqlstore/params.go +++ b/server/services/store/sqlstore/params.go @@ -25,6 +25,7 @@ type Params struct { IsSingleUser bool NewMutexFn MutexFactory ServicesAPI servicesAPI + SkipMigrations bool } func (p Params) CheckValid() error { diff --git a/server/services/store/sqlstore/public_methods.go b/server/services/store/sqlstore/public_methods.go index adee06a61..bd4435990 100644 --- a/server/services/store/sqlstore/public_methods.go +++ b/server/services/store/sqlstore/public_methods.go @@ -304,11 +304,6 @@ func (s *SQLStore) GetBlocksForBoard(boardID string) ([]model.Block, error) { } -func (s *SQLStore) GetBlocksWithBoardID(boardID string) ([]model.Block, error) { - return s.getBlocksWithBoardID(s.db, boardID) - -} - func (s *SQLStore) GetBlocksWithParent(boardID string, parentID string) ([]model.Block, error) { return s.getBlocksWithParent(s.db, boardID, parentID) @@ -469,6 +464,11 @@ func (s *SQLStore) GetTeam(ID string) (*model.Team, error) { } +func (s *SQLStore) GetTeamBoardsInsights(teamID string, userID string, since int64, offset int, limit int, boardIDs []string) (*model.BoardInsightsList, error) { + return s.getTeamBoardsInsights(s.db, teamID, userID, since, offset, limit, boardIDs) + +} + func (s *SQLStore) GetTeamCount() (int64, error) { return s.getTeamCount(s.db) @@ -489,6 +489,11 @@ func (s *SQLStore) GetUsedCardsCount() (int, error) { } +func (s *SQLStore) GetUserBoardsInsights(teamID string, userID string, since int64, offset int, limit int, boardIDs []string) (*model.BoardInsightsList, error) { + return s.getUserBoardsInsights(s.db, teamID, userID, since, offset, limit, boardIDs) + +} + func (s *SQLStore) GetUserByEmail(email string) (*model.User, error) { return s.getUserByEmail(s.db, email) @@ -509,11 +514,21 @@ func (s *SQLStore) GetUserCategoryBoards(userID string, teamID string) ([]model. } +func (s *SQLStore) GetUserTimezone(userID string) (string, error) { + return s.getUserTimezone(s.db, userID) + +} + func (s *SQLStore) GetUsersByTeam(teamID string) ([]*model.User, error) { return s.getUsersByTeam(s.db, teamID) } +func (s *SQLStore) GetUsersList(userIDs []string) ([]*model.User, error) { + return s.getUsersList(s.db, userIDs) + +} + func (s *SQLStore) InsertBlock(block *model.Block, userID string) error { if s.dbType == model.SqliteDBType { return s.insertBlock(s.db, block, userID) diff --git a/server/services/store/sqlstore/sqlstore.go b/server/services/store/sqlstore/sqlstore.go index e146093c2..b011cf184 100644 --- a/server/services/store/sqlstore/sqlstore.go +++ b/server/services/store/sqlstore/sqlstore.go @@ -2,6 +2,7 @@ package sqlstore import ( "database/sql" + "fmt" "net/url" sq "github.com/Masterminds/squirrel" @@ -59,11 +60,12 @@ func New(params Params) (*SQLStore, error) { return nil, err } - err = store.Migrate() - if err != nil { - params.Logger.Error(`Table creation / migration failed`, mlog.Err(err)) + if !params.SkipMigrations { + if mErr := store.Migrate(); mErr != nil { + params.Logger.Error(`Table creation / migration failed`, mlog.Err(mErr)) - return nil, err + return nil, mErr + } } return store, nil } @@ -118,6 +120,29 @@ func (s *SQLStore) escapeField(fieldName string) string { return fieldName } +func (s *SQLStore) concatenationSelector(field string, delimiter string) string { + if s.dbType == model.SqliteDBType { + return fmt.Sprintf("group_concat(%s)", field) + } + if s.dbType == model.PostgresDBType { + return fmt.Sprintf("string_agg(%s, '%s')", field, delimiter) + } + if s.dbType == model.MysqlDBType { + return fmt.Sprintf("GROUP_CONCAT(%s SEPARATOR '%s')", field, delimiter) + } + return "" +} + +func (s *SQLStore) elementInColumn(column string) string { + if s.dbType == model.SqliteDBType || s.dbType == model.MysqlDBType { + return fmt.Sprintf("instr(%s, ?) > 0", column) + } + if s.dbType == model.PostgresDBType { + return fmt.Sprintf("position(? in %s) > 0", column) + } + return "" +} + func (s *SQLStore) getLicense(db sq.BaseRunner) *mmModel.License { return nil } diff --git a/server/services/store/sqlstore/sqlstore_test.go b/server/services/store/sqlstore/sqlstore_test.go index 6aed21a06..0fc9aff4c 100644 --- a/server/services/store/sqlstore/sqlstore_test.go +++ b/server/services/store/sqlstore/sqlstore_test.go @@ -6,7 +6,9 @@ package sqlstore import ( "testing" + "github.com/mattermost/focalboard/server/model" "github.com/mattermost/focalboard/server/services/store/storetests" + "github.com/stretchr/testify/require" ) func TestSQLStore(t *testing.T) { @@ -23,4 +25,41 @@ func TestSQLStore(t *testing.T) { t.Run("DataRetention", func(t *testing.T) { storetests.StoreTestDataRetention(t, SetupTests) }) t.Run("CloudStore", func(t *testing.T) { storetests.StoreTestCloudStore(t, SetupTests) }) t.Run("StoreTestFileStore", func(t *testing.T) { storetests.StoreTestFileStore(t, SetupTests) }) + t.Run("StoreTestCategoryStore", func(t *testing.T) { storetests.StoreTestCategoryStore(t, SetupTests) }) + t.Run("StoreTestCategoryBoardsStore", func(t *testing.T) { storetests.StoreTestCategoryBoardsStore(t, SetupTests) }) + t.Run("BoardsInsightsStore", func(t *testing.T) { storetests.StoreTestBoardsInsightsStore(t, SetupTests) }) +} + +// tests for utility functions inside sqlstore.go + +func TestConcatenationSelector(t *testing.T) { + store, tearDown := SetupTests(t) + sqlStore := store.(*SQLStore) + defer tearDown() + + concatenationString := sqlStore.concatenationSelector("a", ",") + switch sqlStore.dbType { + case model.SqliteDBType: + require.Equal(t, concatenationString, "group_concat(a)") + case model.MysqlDBType: + require.Equal(t, concatenationString, "GROUP_CONCAT(a SEPARATOR ',')") + case model.PostgresDBType: + require.Equal(t, concatenationString, "string_agg(a, ',')") + } +} + +func TestElementInColumn(t *testing.T) { + store, tearDown := SetupTests(t) + sqlStore := store.(*SQLStore) + defer tearDown() + + inLiteral := sqlStore.elementInColumn("test_column") + switch sqlStore.dbType { + case model.SqliteDBType: + require.Equal(t, inLiteral, "instr(test_column, ?) > 0") + case model.MysqlDBType: + require.Equal(t, inLiteral, "instr(test_column, ?) > 0") + case model.PostgresDBType: + require.Equal(t, inLiteral, "position(? in test_column) > 0") + } } diff --git a/server/services/store/sqlstore/user.go b/server/services/store/sqlstore/user.go index 3b234e4e1..3fcf9b6cb 100644 --- a/server/services/store/sqlstore/user.go +++ b/server/services/store/sqlstore/user.go @@ -101,6 +101,10 @@ func (s *SQLStore) getUserByID(db sq.BaseRunner, userID string) (*model.User, er return s.getUserByCondition(db, sq.Eq{"id": userID}) } +func (s *SQLStore) getUsersList(db sq.BaseRunner, userIDs []string) ([]*model.User, error) { + return s.getUsersByCondition(db, sq.Eq{"id": userIDs}, 0) +} + func (s *SQLStore) getUserByEmail(db sq.BaseRunner, email string) (*model.User, error) { return s.getUserByCondition(db, sq.Eq{"email": email}) } @@ -274,3 +278,7 @@ func (s *SQLStore) patchUserProps(db sq.BaseRunner, userID string, patch model.U func (s *SQLStore) sendMessage(db sq.BaseRunner, message, postType string, receipts []string) error { return errUnsupportedOperation } + +func (s *SQLStore) getUserTimezone(_ sq.BaseRunner, _ string) (string, error) { + return "", errUnsupportedOperation +} diff --git a/server/services/store/store.go b/server/services/store/store.go index 79d2ac969..87c3daa9f 100644 --- a/server/services/store/store.go +++ b/server/services/store/store.go @@ -17,7 +17,6 @@ type Store interface { GetBlocksWithParentAndType(boardID, parentID string, blockType string) ([]model.Block, error) GetBlocksWithParent(boardID, parentID string) ([]model.Block, error) GetBlocksByIDs(ids []string) ([]model.Block, error) - GetBlocksWithBoardID(boardID string) ([]model.Block, error) GetBlocksWithType(boardID, blockType string) ([]model.Block, error) GetSubTree2(boardID, blockID string, opts model.QuerySubtreeOptions) ([]model.Block, error) GetBlocksForBoard(boardID string) ([]model.Block, error) @@ -55,6 +54,7 @@ type Store interface { GetRegisteredUserCount() (int, error) GetUserByID(userID string) (*model.User, error) + GetUsersList(userIDs []string) ([]*model.User, error) GetUserByEmail(email string) (*model.User, error) GetUserByUsername(username string) (*model.User, error) CreateUser(user *model.User) error @@ -155,6 +155,11 @@ type Store interface { SearchUserChannels(teamID, userID, query string) ([]*mmModel.Channel, error) GetChannel(teamID, channelID string) (*mmModel.Channel, error) SendMessage(message, postType string, receipts []string) error + + // Insights + GetTeamBoardsInsights(teamID string, userID string, since int64, offset int, limit int, boardIDs []string) (*model.BoardInsightsList, error) + GetUserBoardsInsights(teamID string, userID string, since int64, offset int, limit int, boardIDs []string) (*model.BoardInsightsList, error) + GetUserTimezone(userID string) (string, error) } type NotSupportedError struct { diff --git a/server/services/store/storetests/blocks.go b/server/services/store/storetests/blocks.go index e6420a8f1..e18005299 100644 --- a/server/services/store/storetests/blocks.go +++ b/server/services/store/storetests/blocks.go @@ -266,20 +266,6 @@ func testPatchBlock(t *testing.T, store store.Store) { require.Len(t, blocks, initialCount) }) - t.Run("invalid rootid", func(t *testing.T) { - wrongBoardID := "" - blockPatch := model.BlockPatch{ - BoardID: &wrongBoardID, - } - - err := store.PatchBlock("id-test", &blockPatch, "user-id-1") - require.Error(t, err) - - blocks, err := store.GetBlocksForBoard(boardID) - require.NoError(t, err) - require.Len(t, blocks, initialCount) - }) - t.Run("invalid fields data", func(t *testing.T) { blockPatch := model.BlockPatch{ UpdatedFields: map[string]interface{}{"no-serialiable-value": t.Run}, @@ -753,14 +739,14 @@ func testGetBlocks(t *testing.T, store store.Store) { t.Run("not existing board", func(t *testing.T) { time.Sleep(1 * time.Millisecond) - blocks, err = store.GetBlocksWithBoardID("not-exists") + blocks, err = store.GetBlocksForBoard("not-exists") require.NoError(t, err) require.Len(t, blocks, 0) }) t.Run("all blocks of the a board", func(t *testing.T) { time.Sleep(1 * time.Millisecond) - blocks, err = store.GetBlocksWithBoardID(boardID) + blocks, err = store.GetBlocksForBoard(boardID) require.NoError(t, err) require.Len(t, blocks, 5) }) diff --git a/server/services/store/storetests/board_insights.go b/server/services/store/storetests/board_insights.go new file mode 100644 index 000000000..4bc6f0f95 --- /dev/null +++ b/server/services/store/storetests/board_insights.go @@ -0,0 +1,99 @@ +package storetests + +import ( + "strconv" + "testing" + + "github.com/mattermost/focalboard/server/model" + "github.com/mattermost/focalboard/server/services/store" + "github.com/stretchr/testify/require" +) + +const ( + testInsightsUserID1 = "user-id-1" +) + +func StoreTestBoardsInsightsStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) { + t.Run("GetBoardsInsights", func(t *testing.T) { + store, tearDown := setup(t) + defer tearDown() + getBoardsInsightsTest(t, store) + }) +} + +func getBoardsInsightsTest(t *testing.T, store store.Store) { + // creating sample data + teamID := testTeamID + userID := testUserID + newBab := &model.BoardsAndBlocks{ + Boards: []*model.Board{ + {ID: "board-id-1", TeamID: teamID, Type: model.BoardTypeOpen, Icon: "💬"}, + {ID: "board-id-2", TeamID: teamID, Type: model.BoardTypePrivate}, + {ID: "board-id-3", TeamID: teamID, Type: model.BoardTypeOpen}, + }, + Blocks: []model.Block{ + {ID: "block-id-1", BoardID: "board-id-1", Type: model.TypeCard}, + {ID: "block-id-2", BoardID: "board-id-2", Type: model.TypeCard}, + {ID: "block-id-3", BoardID: "board-id-1", Type: model.TypeCard}, + {ID: "block-id-4", BoardID: "board-id-2", Type: model.TypeCard}, + {ID: "block-id-5", BoardID: "board-id-1", Type: model.TypeCard}, + {ID: "block-id-6", BoardID: "board-id-2", Type: model.TypeCard}, + {ID: "block-id-7", BoardID: "board-id-1", Type: model.TypeCard}, + {ID: "block-id-8", BoardID: "board-id-2", Type: model.TypeCard}, + {ID: "block-id-9", BoardID: "board-id-1", Type: model.TypeCard}, + {ID: "block-id-10", BoardID: "board-id-3", Type: model.TypeCard}, + {ID: "block-id-11", BoardID: "board-id-3", Type: model.TypeCard}, + {ID: "block-id-12", BoardID: "board-id-3", Type: model.TypeCard}, + }, + } + + bab, err := store.CreateBoardsAndBlocks(newBab, userID) + require.Nil(t, err) + require.NotNil(t, bab) + + newBab = &model.BoardsAndBlocks{ + Blocks: []model.Block{ + {ID: "block-id-13", BoardID: "board-id-1", Type: model.TypeCard}, + {ID: "block-id-14", BoardID: "board-id-1", Type: model.TypeCard}, + }, + } + bab, err = store.CreateBoardsAndBlocks(newBab, testInsightsUserID1) + require.Nil(t, err) + require.NotNil(t, bab) + bm := &model.BoardMember{ + UserID: userID, + BoardID: "board-id-2", + SchemeAdmin: true, + } + + _, _ = store.SaveMember(bm) + + boardsUser1, _ := store.GetBoardsForUserAndTeam(testUserID, testTeamID) + boardsUser2, _ := store.GetBoardsForUserAndTeam(testInsightsUserID1, testTeamID) + t.Run("team insights", func(t *testing.T) { + boardIDs := []string{boardsUser1[0].ID, boardsUser1[1].ID, boardsUser1[2].ID} + topTeamBoards, err := store.GetTeamBoardsInsights(testTeamID, testUserID, + 0, 0, 10, boardIDs) + require.NoError(t, err) + require.Len(t, topTeamBoards.Items, 3) + // validate board insight content + require.Equal(t, topTeamBoards.Items[0].ActivityCount, strconv.Itoa(8)) + require.Equal(t, topTeamBoards.Items[0].Icon, "💬") + require.Equal(t, topTeamBoards.Items[1].ActivityCount, strconv.Itoa(5)) + require.Equal(t, topTeamBoards.Items[2].ActivityCount, strconv.Itoa(4)) + }) + + t.Run("user insights", func(t *testing.T) { + boardIDs := []string{boardsUser1[0].ID, boardsUser1[1].ID, boardsUser1[2].ID} + topUser1Boards, err := store.GetUserBoardsInsights(testTeamID, testUserID, 0, 0, 10, boardIDs) + require.NoError(t, err) + require.Len(t, topUser1Boards.Items, 3) + require.Equal(t, topUser1Boards.Items[0].Icon, "💬") + require.Equal(t, topUser1Boards.Items[0].BoardID, "board-id-1") + boardIDs = []string{boardsUser2[0].ID, boardsUser2[1].ID} + topUser2Boards, err := store.GetUserBoardsInsights(testTeamID, testInsightsUserID1, 0, 0, 10, boardIDs) + require.NoError(t, err) + require.Len(t, topUser2Boards.Items, 1) + require.Equal(t, topUser2Boards.Items[0].BoardID, "board-id-1") + }) +} diff --git a/server/services/store/storetests/category.go b/server/services/store/storetests/category.go new file mode 100644 index 000000000..67430cd2d --- /dev/null +++ b/server/services/store/storetests/category.go @@ -0,0 +1,205 @@ +package storetests + +import ( + "testing" + + "github.com/mattermost/focalboard/server/model" + "github.com/mattermost/focalboard/server/services/store" + "github.com/mattermost/focalboard/server/utils" + "github.com/stretchr/testify/assert" +) + +func StoreTestCategoryStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) { + t.Run("CreateCategory", func(t *testing.T) { + store, tearDown := setup(t) + defer tearDown() + testGetCreateCategory(t, store) + }) + t.Run("UpdateCategory", func(t *testing.T) { + store, tearDown := setup(t) + defer tearDown() + testUpdateCategory(t, store) + }) + t.Run("DeleteCategory", func(t *testing.T) { + store, tearDown := setup(t) + defer tearDown() + testDeleteCategory(t, store) + }) + t.Run("GetUserCategoriesCategory", func(t *testing.T) { + store, tearDown := setup(t) + defer tearDown() + testGetUserCategories(t, store) + }) +} + +func testGetCreateCategory(t *testing.T, store store.Store) { + t.Run("save uncollapsed category", func(t *testing.T) { + now := utils.GetMillis() + category := model.Category{ + ID: "category_id_1", + Name: "Category", + UserID: "user_id_1", + TeamID: "team_id_1", + CreateAt: now, + UpdateAt: now, + DeleteAt: 0, + Collapsed: false, + } + + err := store.CreateCategory(category) + assert.NoError(t, err) + + createdCategory, err := store.GetCategory("category_id_1") + assert.NoError(t, err) + assert.Equal(t, "Category", createdCategory.Name) + assert.Equal(t, "user_id_1", createdCategory.UserID) + assert.Equal(t, "team_id_1", createdCategory.TeamID) + assert.Equal(t, false, createdCategory.Collapsed) + }) + + t.Run("save collapsed category", func(t *testing.T) { + now := utils.GetMillis() + category := model.Category{ + ID: "category_id_2", + Name: "Category", + UserID: "user_id_1", + TeamID: "team_id_1", + CreateAt: now, + UpdateAt: now, + DeleteAt: 0, + Collapsed: true, + } + + err := store.CreateCategory(category) + assert.NoError(t, err) + + createdCategory, err := store.GetCategory("category_id_2") + assert.NoError(t, err) + assert.Equal(t, "Category", createdCategory.Name) + assert.Equal(t, "user_id_1", createdCategory.UserID) + assert.Equal(t, "team_id_1", createdCategory.TeamID) + assert.Equal(t, true, createdCategory.Collapsed) + }) +} + +func testUpdateCategory(t *testing.T, store store.Store) { + now := utils.GetMillis() + category := model.Category{ + ID: "category_id_1", + Name: "Category 1", + UserID: "user_id_1", + TeamID: "team_id_1", + CreateAt: now, + UpdateAt: now, + DeleteAt: 0, + Collapsed: false, + } + + err := store.CreateCategory(category) + assert.NoError(t, err) + + updateNow := utils.GetMillis() + updatedCategory := model.Category{ + ID: "category_id_1", + Name: "Category 1 New", + UserID: "user_id_1", + TeamID: "team_id_1", + CreateAt: now, + UpdateAt: updateNow, + DeleteAt: 0, + Collapsed: true, + } + + err = store.UpdateCategory(updatedCategory) + assert.NoError(t, err) + + fetchedCategory, err := store.GetCategory("category_id_1") + assert.NoError(t, err) + assert.Equal(t, "category_id_1", fetchedCategory.ID) + assert.Equal(t, "Category 1 New", fetchedCategory.Name) + assert.Equal(t, true, fetchedCategory.Collapsed) + + // now lets try to un-collapse the same category + updatedCategory.Collapsed = false + err = store.UpdateCategory(updatedCategory) + assert.NoError(t, err) + + fetchedCategory, err = store.GetCategory("category_id_1") + assert.NoError(t, err) + assert.Equal(t, "category_id_1", fetchedCategory.ID) + assert.Equal(t, "Category 1 New", fetchedCategory.Name) + assert.Equal(t, false, fetchedCategory.Collapsed) +} + +func testDeleteCategory(t *testing.T, store store.Store) { + now := utils.GetMillis() + category := model.Category{ + ID: "category_id_1", + Name: "Category 1", + UserID: "user_id_1", + TeamID: "team_id_1", + CreateAt: now, + UpdateAt: now, + DeleteAt: 0, + Collapsed: false, + } + + err := store.CreateCategory(category) + assert.NoError(t, err) + + err = store.DeleteCategory("category_id_1", "user_id_1", "team_id_1") + assert.NoError(t, err) + + deletedCategory, err := store.GetCategory("category_id_1") + assert.NoError(t, err) + assert.Equal(t, "category_id_1", deletedCategory.ID) + assert.Equal(t, "Category 1", deletedCategory.Name) + assert.Equal(t, false, deletedCategory.Collapsed) + assert.Greater(t, deletedCategory.DeleteAt, int64(0)) +} + +func testGetUserCategories(t *testing.T, store store.Store) { + now := utils.GetMillis() + category1 := model.Category{ + ID: "category_id_1", + Name: "Category 1", + UserID: "user_id_1", + TeamID: "team_id_1", + CreateAt: now, + UpdateAt: now, + DeleteAt: 0, + Collapsed: false, + } + err := store.CreateCategory(category1) + assert.NoError(t, err) + + category2 := model.Category{ + ID: "category_id_2", + Name: "Category 2", + UserID: "user_id_1", + TeamID: "team_id_1", + CreateAt: now, + UpdateAt: now, + DeleteAt: 0, + Collapsed: false, + } + err = store.CreateCategory(category2) + assert.NoError(t, err) + + category3 := model.Category{ + ID: "category_id_3", + Name: "Category 2", + UserID: "user_id_1", + TeamID: "team_id_1", + CreateAt: now, + UpdateAt: now, + DeleteAt: 0, + Collapsed: false, + } + err = store.CreateCategory(category3) + assert.NoError(t, err) + + userCategories, err := store.GetUserCategoryBoards("user_id_1", "team_id_1") + assert.NoError(t, err) + assert.Equal(t, 3, len(userCategories)) +} diff --git a/server/services/store/storetests/categoryBoards.go b/server/services/store/storetests/categoryBoards.go new file mode 100644 index 000000000..4e365c552 --- /dev/null +++ b/server/services/store/storetests/categoryBoards.go @@ -0,0 +1,104 @@ +package storetests + +import ( + "testing" + + "github.com/mattermost/focalboard/server/model" + "github.com/mattermost/focalboard/server/services/store" + "github.com/mattermost/focalboard/server/utils" + "github.com/stretchr/testify/assert" +) + +func StoreTestCategoryBoardsStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) { + t.Run("GetUserCategoryBoards", func(t *testing.T) { + store, tearDown := setup(t) + defer tearDown() + testGetUserCategoryBoards(t, store) + }) +} + +func testGetUserCategoryBoards(t *testing.T, store store.Store) { + now := utils.GetMillis() + category1 := model.Category{ + ID: "category_id_1", + Name: "Category 1", + UserID: "user_id_1", + TeamID: "team_id_1", + CreateAt: now, + UpdateAt: now, + DeleteAt: 0, + Collapsed: false, + } + err := store.CreateCategory(category1) + assert.NoError(t, err) + + category2 := model.Category{ + ID: "category_id_2", + Name: "Category 2", + UserID: "user_id_1", + TeamID: "team_id_1", + CreateAt: now, + UpdateAt: now, + DeleteAt: 0, + Collapsed: false, + } + err = store.CreateCategory(category2) + assert.NoError(t, err) + + category3 := model.Category{ + ID: "category_id_3", + Name: "Category 3", + UserID: "user_id_1", + TeamID: "team_id_1", + CreateAt: now, + UpdateAt: now, + DeleteAt: 0, + Collapsed: false, + } + err = store.CreateCategory(category3) + assert.NoError(t, err) + + // Adding Board 1 and Board 2 to Category 1 + // The boards don't need to exists in DB for this test + err = store.AddUpdateCategoryBoard("user_id_1", "category_id_1", "board_1") + assert.NoError(t, err) + + err = store.AddUpdateCategoryBoard("user_id_1", "category_id_1", "board_2") + assert.NoError(t, err) + + // Adding Board 3 to Category 2 + err = store.AddUpdateCategoryBoard("user_id_1", "category_id_2", "board_3") + assert.NoError(t, err) + + // we'll leave category 3 empty + + userCategoryBoards, err := store.GetUserCategoryBoards("user_id_1", "team_id_1") + assert.NoError(t, err) + + // we created 3 categories for the user + assert.Equal(t, 3, len(userCategoryBoards)) + + var category1BoardCategory model.CategoryBoards + var category2BoardCategory model.CategoryBoards + var category3BoardCategory model.CategoryBoards + + for i := range userCategoryBoards { + switch userCategoryBoards[i].ID { + case "category_id_1": + category1BoardCategory = userCategoryBoards[i] + case "category_id_2": + category2BoardCategory = userCategoryBoards[i] + case "category_id_3": + category3BoardCategory = userCategoryBoards[i] + } + } + + assert.NotEmpty(t, category1BoardCategory) + assert.Equal(t, 2, len(category1BoardCategory.BoardIDs)) + + assert.NotEmpty(t, category1BoardCategory) + assert.Equal(t, 1, len(category2BoardCategory.BoardIDs)) + + assert.NotEmpty(t, category1BoardCategory) + assert.Equal(t, 0, len(category3BoardCategory.BoardIDs)) +} diff --git a/server/services/store/storetests/data_retention.go b/server/services/store/storetests/data_retention.go index 829cdabc5..a1e53d367 100644 --- a/server/services/store/storetests/data_retention.go +++ b/server/services/store/storetests/data_retention.go @@ -100,7 +100,7 @@ func LoadData(t *testing.T, store store.Store) { func testRunDataRetention(t *testing.T, store store.Store, batchSize int) { LoadData(t, store) - blocks, err := store.GetBlocksWithBoardID(boardID) + blocks, err := store.GetBlocksForBoard(boardID) require.NoError(t, err) require.Len(t, blocks, 4) initialCount := len(blocks) @@ -117,7 +117,7 @@ func testRunDataRetention(t *testing.T, store store.Store, batchSize int) { require.True(t, deletions > int64(initialCount)) // expect all blocks to be deleted. - blocks, errBlocks := store.GetBlocksWithBoardID(boardID) + blocks, errBlocks := store.GetBlocksForBoard(boardID) require.NoError(t, errBlocks) require.Equal(t, 0, len(blocks)) diff --git a/server/ws/mocks/mockpluginapi.go b/server/ws/mocks/mockpluginapi.go index b206e8383..26bb2578d 100644 --- a/server/ws/mocks/mockpluginapi.go +++ b/server/ws/mocks/mockpluginapi.go @@ -681,21 +681,6 @@ func (mr *MockAPIMockRecorder) GetChannelsForTeamForUser(arg0, arg1, arg2 interf return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChannelsForTeamForUser", reflect.TypeOf((*MockAPI)(nil).GetChannelsForTeamForUser), arg0, arg1, arg2) } -// GetCloudLimits mocks base method. -func (m *MockAPI) GetCloudLimits() (*model.ProductLimits, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetCloudLimits") - ret0, _ := ret[0].(*model.ProductLimits) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetCloudLimits indicates an expected call of GetCloudLimits. -func (mr *MockAPIMockRecorder) GetCloudLimits() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCloudLimits", reflect.TypeOf((*MockAPI)(nil).GetCloudLimits)) -} - // GetCommand mocks base method. func (m *MockAPI) GetCommand(arg0 string) (*model.Command, error) { m.ctrl.T.Helper() diff --git a/server/ws/plugin_adapter.go b/server/ws/plugin_adapter.go index bc029be54..7da35dac5 100644 --- a/server/ws/plugin_adapter.go +++ b/server/ws/plugin_adapter.go @@ -486,8 +486,8 @@ func (pa *PluginAdapter) BroadcastCategoryChange(category model.Category) { go func() { clusterMessage := &ClusterMessage{ - Payload: payload, - EnsureUsers: []string{category.UserID}, + Payload: payload, + UserID: category.UserID, } pa.sendMessageToCluster("websocket_message", clusterMessage) @@ -511,7 +511,18 @@ func (pa *PluginAdapter) BroadcastCategoryBoardChange(teamID, userID string, boa BoardCategories: &boardCategory, } - pa.sendTeamMessage(websocketActionUpdateCategoryBoard, teamID, utils.StructToMap(message)) + payload := utils.StructToMap(message) + + go func() { + clusterMessage := &ClusterMessage{ + Payload: payload, + UserID: userID, + } + + pa.sendMessageToCluster("websocket_message", clusterMessage) + }() + + pa.sendUserMessageSkipCluster(websocketActionUpdateCategoryBoard, utils.StructToMap(message), userID) } func (pa *PluginAdapter) BroadcastBlockDelete(teamID, blockID, boardID string) { @@ -564,7 +575,7 @@ func (pa *PluginAdapter) BroadcastMemberChange(teamID, boardID string, member *m Member: member, } - pa.sendBoardMessage(teamID, boardID, utils.StructToMap(message)) + pa.sendBoardMessage(teamID, boardID, utils.StructToMap(message), member.UserID) } func (pa *PluginAdapter) BroadcastMemberDelete(teamID, boardID, userID string) { diff --git a/server/ws/plugin_adapter_cluster.go b/server/ws/plugin_adapter_cluster.go index f05628eec..754b86afa 100644 --- a/server/ws/plugin_adapter_cluster.go +++ b/server/ws/plugin_adapter_cluster.go @@ -10,6 +10,7 @@ import ( type ClusterMessage struct { TeamID string BoardID string + UserID string Payload map[string]interface{} EnsureUsers []string } @@ -69,5 +70,10 @@ func (pa *PluginAdapter) HandleClusterEvent(ev mmModel.PluginClusterEvent) { return } + if clusterMessage.UserID != "" { + pa.sendUserMessageSkipCluster(action, clusterMessage.Payload, clusterMessage.UserID) + return + } + pa.sendTeamMessageSkipCluster(action, clusterMessage.TeamID, clusterMessage.Payload) } diff --git a/webapp/cypress/integration/cardBadges.ts b/webapp/cypress/integration/cardBadges.ts index 8874e6135..4194a98e5 100644 --- a/webapp/cypress/integration/cardBadges.ts +++ b/webapp/cypress/integration/cardBadges.ts @@ -54,8 +54,8 @@ describe('Card badges', () => { // Hide card badges cy.log('**Hide card badges**') - cy.findByRole('button', {name: 'Properties menu'}).click() cy.findByRole('button', {name: 'Comments and description'}).click() + cy.findByRole('button', {name: 'Properties menu'}).click() cy.findByTitle('This card has a description').should('not.exist') cy.findByTitle('Comments').should('not.exist') cy.findByTitle('Checkboxes').should('not.exist') diff --git a/webapp/cypress/integration/cardURLProperty.ts b/webapp/cypress/integration/cardURLProperty.ts index 9bdbf8f5c..959ee652c 100644 --- a/webapp/cypress/integration/cardURLProperty.ts +++ b/webapp/cypress/integration/cardURLProperty.ts @@ -99,7 +99,7 @@ describe('Card URL Property', () => { cy.log(`**Add ${type} view**`) // Intercept and wait for getUser request because it is the last one in the effects for BoardPage // After this last request the BoardPage component will not have additional rerenders - cy.intercept('GET', '/api/v2/users/u*').as('getUser') + cy.intercept('POST', '/api/v2/users').as('getUser') cy.findByRole('button', {name: 'View menu'}).click() cy.findByText('Add view').realHover() cy.findByRole('button', {name: type}).click() diff --git a/webapp/cypress/support/api_commands.ts b/webapp/cypress/support/api_commands.ts index 428926601..e3e96066a 100644 --- a/webapp/cypress/support/api_commands.ts +++ b/webapp/cypress/support/api_commands.ts @@ -3,6 +3,8 @@ import {Board} from '../../src/blocks/board' import {UserConfigPatch} from '../../src/user' +import {versionProperty} from '../../src/store/users' + Cypress.Commands.add('apiRegisterUser', (data: Cypress.UserData, token?: string, failOnError?: boolean) => { return cy.request({ @@ -86,6 +88,7 @@ Cypress.Commands.add('apiSkipTour', (userID: string) => { const body: UserConfigPatch = { updatedFields: { focalboard_welcomePageViewed: '1', + [versionProperty]: 'true', }, } diff --git a/webapp/i18n/ar.json b/webapp/i18n/ar.json index ac3f5b03d..c56e44a89 100644 --- a/webapp/i18n/ar.json +++ b/webapp/i18n/ar.json @@ -39,11 +39,6 @@ "Filter.is-not-empty": "ليس فارغًا", "FilterComponent.add-filter": "+ إضافة فلتر", "FilterComponent.delete": "حذف", - "GalleryCard.copiedLink": "تم النسخ!", - "GalleryCard.delete": "حذف", - "GalleryCard.duplicate": "تكرار", - "KanbanCard.copyLink": "نسخ الرابط", - "KanbanCard.delete": "حذف", "OnboardingTour.AddComments.Title": "إضافة تعليقات", "OnboardingTour.AddDescription.Title": "اضافة وصف", "OnboardingTour.AddProperties.Title": "إضافة خواص", @@ -79,7 +74,6 @@ "ViewHeader.add-template": "نموذج جديد", "ViewHeader.delete-template": "حذف", "ViewHeader.new": "جديز", - "ViewHeader.search": "البحث", "ViewTitle.pick-icon": "اختر أيقونة", "default-properties.title": "العنوان", "login.log-in-button": "لِج", diff --git a/webapp/i18n/ca.json b/webapp/i18n/ca.json index 0f86f7de0..560ddf0d8 100644 --- a/webapp/i18n/ca.json +++ b/webapp/i18n/ca.json @@ -39,11 +39,7 @@ "Filter.not-includes": "no inclou", "FilterComponent.add-filter": "+ Afegeix filtre", "FilterComponent.delete": "Eliminar", - "GalleryCard.delete": "Eliminar", - "GalleryCard.duplicate": "Duplicar", "GroupBy.ungroup": "Desagrupar", - "KanbanCard.delete": "Eliminar", - "KanbanCard.duplicate": "Duplicar", "KanbanCard.untitled": "Sense títol", "Mutator.new-card-from-template": "nova targeta des de plantilla", "Mutator.new-template-from-card": "nova plantilla des de targeta", @@ -119,7 +115,6 @@ "ViewHeader.group-by": "Agrupar per: {property}", "ViewHeader.new": "Nou", "ViewHeader.properties": "Propietats", - "ViewHeader.search": "Cercar", "ViewHeader.search-text": "Cercar text", "ViewHeader.select-a-template": "Selecciona una plantilla", "ViewHeader.sort": "Ordenar", diff --git a/webapp/i18n/de.json b/webapp/i18n/de.json index 09863c6ad..c7b1720de 100644 --- a/webapp/i18n/de.json +++ b/webapp/i18n/de.json @@ -8,21 +8,25 @@ "BoardComponent.no-property-title": "Elemente mit einer leeren {property} Eigenschaft erscheinen hier. Diese Spalte kann nicht entfernt werden.", "BoardComponent.show": "Anzeigen", "BoardMember.schemeAdmin": "Administrator", + "BoardMember.schemeCommenter": "Kommentator", "BoardMember.schemeEditor": "Bearbeiter", "BoardMember.schemeNone": "Keine", "BoardMember.schemeViewer": "Viewer", + "BoardMember.schemeViwer": "Betrachter", + "BoardMember.unlinkChannel": "Verknüpfung aufheben", "BoardPage.newVersion": "Eine neue Version von Boards ist verfügbar, klicke hier, um neu zu laden.", "BoardPage.syncFailed": "Das Board kann gelöscht oder der Zugang entzogen werden.", "BoardTemplateSelector.add-template": "Neue Vorlage", "BoardTemplateSelector.create-empty-board": "Leeres Board erstellen", "BoardTemplateSelector.delete-template": "Löschen", - "BoardTemplateSelector.description": "Wähle eine Vorlage um zu starten. Passe die Vorlage einfach an deine Anforderungen an oder erstelle ein leeres Board.", + "BoardTemplateSelector.description": "Füge ein Board hinzu, indem du eine der unten definierten Vorlagen verwendest oder ganz neu beginnst.", "BoardTemplateSelector.edit-template": "Bearbeiten", - "BoardTemplateSelector.plugin.no-content-description": "Füge ein Board zur Seitenleiste hinzu, indem du eine der Vorlagen unten verwendest oder starte mit einem leeren Board.{lineBreak} Mitglieder von \"{teamName}\" werden Zugriff auf die Boards haben, die hier erstellt werden.", - "BoardTemplateSelector.plugin.no-content-title": "Erstelle ein Board in {teamName}", + "BoardTemplateSelector.plugin.no-content-description": "Füge ein Board zur Seitenleiste hinzu, indem du eine der Vorlagen unten verwendest oder starte mit einem leeren Board.", + "BoardTemplateSelector.plugin.no-content-title": "Erstelle ein Board", "BoardTemplateSelector.title": "Erstelle ein Board", "BoardTemplateSelector.use-this-template": "Verwende diese Vorlage", "BoardsSwitcher.Title": "Finde Boards", + "BoardsUnfurl.Limited": "Weitere Details sind versteckt, da die Karte archiviert wurde", "BoardsUnfurl.Remainder": "+{remainder} mehr", "BoardsUnfurl.Updated": "Aktualisiert {time}", "Calculations.Options.average.displayName": "Durchschnitt", @@ -30,13 +34,13 @@ "Calculations.Options.count.displayName": "Zählen", "Calculations.Options.count.label": "Zählen", "Calculations.Options.countChecked.displayName": "Geprüft", - "Calculations.Options.countChecked.label": "Zählung geprüft", + "Calculations.Options.countChecked.label": "Zähle Markierte", "Calculations.Options.countUnchecked.displayName": "Ungeprüft", - "Calculations.Options.countUnchecked.label": "Zählung ungeprüft", + "Calculations.Options.countUnchecked.label": "Zähle Unmarkierte", "Calculations.Options.countUniqueValue.displayName": "Eindeutig", - "Calculations.Options.countUniqueValue.label": "Eindeutige Werte zählen", + "Calculations.Options.countUniqueValue.label": "Zähle eindeutige Werte", "Calculations.Options.countValue.displayName": "Werte", - "Calculations.Options.countValue.label": "Zählwert", + "Calculations.Options.countValue.label": "Zähle Wert", "Calculations.Options.dateRange.displayName": "Bereich", "Calculations.Options.dateRange.label": "Bereich", "Calculations.Options.earliest.displayName": "Früheste", @@ -52,13 +56,18 @@ "Calculations.Options.none.displayName": "Berechnen", "Calculations.Options.none.label": "Keine", "Calculations.Options.percentChecked.displayName": "Geprüft", - "Calculations.Options.percentChecked.label": "Prozentsatz geprüft", + "Calculations.Options.percentChecked.label": "Prozentsatz Markiert", "Calculations.Options.percentUnchecked.displayName": "Ungeprüft", - "Calculations.Options.percentUnchecked.label": "Prozentsatz ungeprüft", + "Calculations.Options.percentUnchecked.label": "Prozentsatz Unmarkiert", "Calculations.Options.range.displayName": "Bereich", "Calculations.Options.range.label": "Bereich", "Calculations.Options.sum.displayName": "Summe", "Calculations.Options.sum.label": "Summe", + "CalendarCard.untitled": "Ohne Titel", + "CardActionsMenu.copiedLink": "Kopiert!", + "CardActionsMenu.copyLink": "Verknüpfung kopieren", + "CardActionsMenu.delete": "Löschen", + "CardActionsMenu.duplicate": "Duplizieren", "CardBadges.title-checkboxes": "Checkboxen", "CardBadges.title-comments": "Kommentare", "CardBadges.title-description": "Diese Karte hat eine Beschreibung", @@ -68,20 +77,21 @@ "CardDetail.add-icon": "Symbol hinzufügen", "CardDetail.add-property": "+ Eigenschaft hinzufügen", "CardDetail.addCardText": "Kartentext hinzufügen", + "CardDetail.limited-body": "Aktualisiere auf unseren Professional oder Enterprise Plan um archivierte Karten zu betrachten, unbeschränkte Sichten pro Board, unbeschränkte Karten und mehr zu haben.", + "CardDetail.limited-button": "Aktualisiere", + "CardDetail.limited-title": "Diese Karte ist versteckt", "CardDetail.moveContent": "Karteninhalt verschieben", "CardDetail.new-comment-placeholder": "Kommentar hinzufügen...", "CardDetailProperty.confirm-delete-heading": "Eigenschaft löschen bestätigen", "CardDetailProperty.confirm-delete-subtext": "Bist du sicher, dass du die Eigenschaft \"{propertyName}\" löschen möchtest? Wenn du diese löscht, wird die Eigenschaft von allen Karten in diesem Board gelöscht.", "CardDetailProperty.confirm-property-name-change-subtext": "Bist du sicher, dass du die Eigenschaft \"{propertyName}\" {customText} ändern möchtest? Dies wird Werte auf {numOfCards} Karten in diesem Board ändern und kann dazu führen, dass diese verloren gehen.", - "CardDetailProperty.confirm-property-type-change": "Eigenschaftstyp-Änderung bestätigen!", + "CardDetailProperty.confirm-property-type-change": "Bestätige Eigenschaftsänderung", "CardDetailProperty.delete-action-button": "Löschen", - "CardDetailProperty.property-change-action-button": "Eigenschaft ändern", + "CardDetailProperty.property-change-action-button": "Ändere Eigenschaft", "CardDetailProperty.property-changed": "Eigenschaft erfolgreich geändert!", "CardDetailProperty.property-deleted": "{propertyName} erfolgreich gelöscht!", "CardDetailProperty.property-name-change-subtext": "Typ von \"{oldPropType}\" zu \"{newPropType}\"", - "CardDetailProperty.property-type-change-subtext": "Name zu \"{newPropName}\"", - "CardDialog.copiedLink": "Kopiert!", - "CardDialog.copyLink": "Link kopieren", + "CardDetial.limited-link": "Erfahre mehr über unsere Pläne.", "CardDialog.delete-confirmation-dialog-button-text": "Löschen", "CardDialog.delete-confirmation-dialog-heading": "Karte wirklich löschen!", "CardDialog.editing-template": "Du bearbeitest eine Vorlage.", @@ -92,6 +102,7 @@ "Categories.CreateCategoryDialog.UpdateText": "Aktualisieren", "CenterPanel.Login": "Anmeldung", "CenterPanel.Share": "Teilen", + "CloudMessage.cloud-server": "Hole deine eigenen freien Cloud Server.", "ColorOption.selectColor": "Wähle Farbe {color}", "Comment.delete": "Löschen", "CommentsList.send": "Abschicken", @@ -118,7 +129,8 @@ "DeleteBoardDialog.confirm-cancel": "Abbrechen", "DeleteBoardDialog.confirm-delete": "Löschen", "DeleteBoardDialog.confirm-info": "Bist du sicher, dass du das Board \"{boardTitle}\" löschen möchtest? Wenn du es löschen, werden allen Karten auf diesem Board gelöscht.", - "DeleteBoardDialog.confirm-tite": "Bestätigen Board löschen", + "DeleteBoardDialog.confirm-info-template": "Bist du sicher, dass du die Board-Vorlage \"{boardTitle}\" löschen willst?", + "DeleteBoardDialog.confirm-tite": "Board löschen bestätigen", "DeleteBoardDialog.confirm-tite-template": "Board Vorlage wirklich löschen", "Dialog.closeDialog": "Dialog schließen", "EditableDayPicker.today": "Heute", @@ -130,23 +142,17 @@ "Filter.not-includes": "beinhaltet nicht", "FilterComponent.add-filter": "+ Filter hinzufügen", "FilterComponent.delete": "Löschen", - "FindBoFindBoardsDialog.IntroText": "Suche nach Boards", + "FindBoardsDialog.IntroText": "Suche nach Boards", "FindBoardsDialog.NoResultsFor": "Keine Ergebnisse für \"{searchQuery}\"", "FindBoardsDialog.NoResultsSubtext": "Prüfe die Schreibweise oder versuche eine weitere Suche.", "FindBoardsDialog.SubTitle": "Tippe um ein Board zu finden. Benutze HOCH/RUNTER zum Browsen. ENTER zur Auswahl, ESC zum Schließen", "FindBoardsDialog.Title": "Finde Boards", - "GalleryCard.copiedLink": "Kopiert!", - "GalleryCard.copyLink": "Link kopieren", - "GalleryCard.delete": "Löschen", - "GalleryCard.duplicate": "Duplizieren", "GroupBy.hideEmptyGroups": "Verstecke {count} leere Gruppen", "GroupBy.showHiddenGroups": "Zeige {count} versteckte Gruppen", "GroupBy.ungroup": "Gruppierung aufheben", - "KanbanCard.copiedLink": "Kopiert!", - "KanbanCard.copyLink": "Link kopieren", - "KanbanCard.delete": "Löschen", - "KanbanCard.duplicate": "Duplizieren", + "HideBoard.MenuOption": "Board verstecken", "KanbanCard.untitled": "Unbenannt", + "Mutator.new-board-from-template": "Neues Board aus Vorlage", "Mutator.new-card-from-template": "neue Karte aus Vorlage", "Mutator.new-template-from-card": "neue Vorlage aus Karte", "OnboardingTour.AddComments.Body": "Du kannst Themen kommentieren und sogar deine Mattermost-Kollegen @erwähnen, um deren Aufmerksamkeit zu erhalten.", @@ -189,15 +195,16 @@ "RegistrationLink.description": "Teile diesen Link mit anderen zur Accounterstellung:", "RegistrationLink.regenerateToken": "Token neu generieren", "RegistrationLink.tokenRegenerated": "Registrierungslink neu generiert", - "ShareBoard.PublishDescription": "Veröffentliche und teile einen \"Nur Lesen\"-Link mit jedem im Web", + "ShareBoard.PublishDescription": "Veröffentliche und teile einen \"Nur Lesen\"-Link mit jedem im Web.", "ShareBoard.PublishTitle": "Im Web veröffentlichen", "ShareBoard.ShareInternal": "Intern teilen", - "ShareBoard.ShareInternalDescription": "Benutzer mit Berechtigungen können diesen Link benutzen", + "ShareBoard.ShareInternalDescription": "Benutzer mit Berechtigungen können diesen Link benutzen.", "ShareBoard.Title": "Board teilen", "ShareBoard.confirmRegenerateToken": "Diese Aktion invalidiert zuvor geteilte Links. Trotzdem fortfahren?", "ShareBoard.copiedLink": "Kopiert!", "ShareBoard.copyLink": "Link kopieren", "ShareBoard.regenerate": "Token neu erstellen", + "ShareBoard.searchPlaceholder": "Suche nach Personen und Kanälen", "ShareBoard.teamPermissionsText": "Jeder im {teamName} Team", "ShareBoard.tokenRegenrated": "Token neu generiert", "ShareBoard.userPermissionsRemoveMemberText": "Mitglied entfernen", @@ -244,9 +251,16 @@ "URLProperty.copiedLink": "Kopiert!", "URLProperty.copy": "Kopieren", "URLProperty.edit": "Bearbeiten", + "UndoRedoHotKeys.canRedo": "Wiederherstellen", + "UndoRedoHotKeys.canRedo-with-description": "{description} wiederherstellen", + "UndoRedoHotKeys.canUndo": "Rückgängig", + "UndoRedoHotKeys.canUndo-with-description": "{description} rückgängig machen", + "UndoRedoHotKeys.cannotRedo": "Nichts wiederherstellbar", + "UndoRedoHotKeys.cannotUndo": "Nichts zum rückgängig machen", "ValueSelector.noOptions": "Keine Optionen. Fange an zu tippen, um die erste Option hinzuzufügen!", "ValueSelector.valueSelector": "Werteselektor", "ValueSelectorLabel.openMenu": "Menü öffnen", + "VersionMessage.help": "Finde raus, was es Neues in dieser Version gibt.", "View.AddView": "Ansicht hinzufügen", "View.Board": "Board", "View.DeleteView": "Ansicht löschen", @@ -256,6 +270,7 @@ "View.NewCalendarTitle": "Kalenderansicht", "View.NewGalleryTitle": "Galerie Ansicht", "View.NewTableTitle": "Tabellenansicht", + "View.NewTemplateTitle": "Unbenannte Vorlage", "View.Table": "Tabelle", "ViewHeader.add-template": "+ Neue Vorlage", "ViewHeader.delete-template": "Löschen", @@ -271,7 +286,6 @@ "ViewHeader.new": "Neu", "ViewHeader.properties": "Eigenschaften", "ViewHeader.properties-menu": "Eigenschaften Menü", - "ViewHeader.search": "Suchen", "ViewHeader.search-text": "Suche Karten", "ViewHeader.select-a-template": "Vorlage auswählen", "ViewHeader.set-default-template": "Als Standard eingestellt", @@ -279,34 +293,90 @@ "ViewHeader.untitled": "Unbenannt", "ViewHeader.view-header-menu": "Kopfmenü ansehen", "ViewHeader.view-menu": "Ansichten Menü", + "ViewLimitDialog.Heading": "Ansichten pro Board Limit erreicht", + "ViewLimitDialog.PrimaryButton.Title.Admin": "Aktualisieren", + "ViewLimitDialog.PrimaryButton.Title.RegularUser": "Admin benachrichtigen", + "ViewLimitDialog.Subtext.Admin": "Aktualisiere auf unseren Professional oder Enterprise Plan um unbeschränkte Ansichten pro Board, unbeschränkte Karten und mehr zu bekommen.", + "ViewLimitDialog.Subtext.Admin.PricingPageLink": "Erfahre mehr über unsere Pläne.", + "ViewLimitDialog.Subtext.RegularUser": "Benachrichtige deinen Admin um auf unseren Professional oder Enterprise Plan zu aktualisieren um unbeschränkte Ansichten pro Board, unbeschränkte Karten und mehr zu bekommen.", + "ViewLimitDialog.UpgradeImg.AltText": "Bild aktualisieren", + "ViewLimitDialog.notifyAdmin.Success": "Dein Admin wurde benachrichtigt", "ViewTitle.hide-description": "Beschreibung ausblenden", "ViewTitle.pick-icon": "Symbol auswählen", "ViewTitle.random-icon": "Zufällig", "ViewTitle.remove-icon": "Symbol entfernen", "ViewTitle.show-description": "Beschreibung anzeigen", "ViewTitle.untitled-board": "Unbenanntes Board", - "WelcomePage.Description": "Boards ist ein Projektmanagement-Tool, das die Definition, Organisation, Verfolgung und Verwaltung von Arbeit in verschiedenen Teams mit Hilfe einer vertrauten Kanban-Board-Ansicht unterstützt", + "WelcomePage.Description": "Boards ist ein Projektmanagement-Tool, das die Definition, Organisation, Verfolgung und Verwaltung von Arbeit in verschiedenen Teams mit Hilfe einer vertrauten Kanban-Board-Ansicht unterstützt.", "WelcomePage.Explore.Button": "Rundgang", "WelcomePage.Heading": "Willkommen bei Boards", "WelcomePage.NoThanks.Text": "Nein danke, ich werde es selbst herausfinden", "Workspace.editing-board-template": "Du bearbeitest eine Board Vorlage.", + "boardSelector.confirm-link-board": "Verknüpfe Board mit Kanal", + "boardSelector.confirm-link-board-button": "Verknüpfe Board", + "boardSelector.confirm-link-board-subtext": "Wenn du \"{boardName}\" mit diesem Kanal verknüpfst, werden alle Mitglieder des Kanals (aktuelle und neue) das Board bearbeiten können. Du kannst die Verknüpfung eine Boards mit einem Kanal jederzeit entfernen.", + "boardSelector.confirm-link-board-subtext-with-other-channel": "Wenn du einen Kanal mit einem Board verknüpfst, werden alle Mitglieder des Kanals (aktuelle und neue) das Board bearbeiten können.{lineBreak}Dieses Board ist mit einem anderen Kanal verknüpft. Die Verknüpfung wird getrennt, wenn du es hier verknüpfst.", + "boardSelector.create-a-board": "Erstelle ein Board", + "boardSelector.link": "Verknüpfung", + "boardSelector.search-for-boards": "Suche nach Boards", + "boardSelector.title": "Verknüpfe Boards", + "boardSelector.unlink": "Verknüpfung aufheben", "calendar.month": "Monat", "calendar.today": "HEUTE", "calendar.week": "Woche", + "cloudMessage.learn-more": "Erfahre mehr", "createImageBlock.failed": "Kann Datei nicht hochladen. Limit für Dateigröße erreicht.", "default-properties.badges": "Kommentare und Beschreibung", "default-properties.title": "Titel", + "error.back-to-home": "Zurück zur Startseite", + "error.back-to-team": "Zurück zum Team", + "error.board-not-found": "Board nicht gefunden.", + "error.go-login": "Anmeldung", + "error.invalid-read-only-board": "Du hast keine Zugriff auf dieses Board. Melde dich an um auf das Board zuzugreifen.", + "error.not-logged-in": "Deine Sitzung könnte abgelaufen sein oder du bist nicht angemeldet. Melde dich nochmal an um auf das Board zuzugreifen.", "error.page.title": "Entschuldigung, etwas ist schief gelaufen", + "error.team-undefined": "Kein gültiges Team.", + "error.unknown": "Ein Fehler ist aufgetreten.", "generic.previous": "Zurück", "imagePaste.upload-failed": "Einige Dateien nicht hochgeladen. Limit für Dateigröße erreicht", + "limitedCard.title": "Versteckte Karten", "login.log-in-button": "Anmelden", "login.log-in-title": "Anmelden", "login.register-button": "oder erstelle einen Account wenn du noch keines hast", + "notification-box-card-limit-reached.close-tooltip": "Für 10 Tage schlummern", + "notification-box-card-limit-reached.contact-link": "Benachrichtige deinen Admin", + "notification-box-card-limit-reached.link": "Wechsel auf einen kostenpflichtigen Plan", + "notification-box-card-limit-reached.title": "{cards} vom Board versteckte Karten", + "notification-box-cards-hidden.title": "Diese Aktion verdeckt eine andere Karte", + "notification-box.card-limit-reached.not-admin.text": "Um auf archivierte Karten zuzugreifen, kannst du {contactLink} um auf einen bezahlten Plan zu wechseln.", + "notification-box.card-limit-reached.text": "Kartenlimit erreicht. Um ältere Karten zu betrachten, {link}", "register.login-button": "oder melde dich an, wenn du bereits ein Konto hast", "register.signup-title": "Registriere dich für deinen Account", + "rhs-boards.add": "Hinzufügen", + "rhs-boards.dm": "DN", + "rhs-boards.gm": "GN", + "rhs-boards.header.dm": "diese Direktnachricht", + "rhs-boards.header.gm": "diese Gruppennachricht", + "rhs-boards.last-update-at": "Letzte Aktualisierung um: {datetime}", + "rhs-boards.link-boards-to-channel": "Verknüpfe Board mit {channelName}", + "rhs-boards.linked-boards": "Verknüpfte Boards", + "rhs-boards.no-boards-linked-to-channel": "Bisher sind keine Boards mit {channelName} verknüpft", + "rhs-boards.no-boards-linked-to-channel-description": "Boards ist ein Projektmanagement Werkzeug, das hilft Aufgaben über Teams hinweg zu definieren, organisieren, verfolgen und verwalten, ähnlich den bekannten Kanban Boards.", + "rhs-boards.unlink-board": "Board Verknüpfung aufheben", + "rhs-channel-boards-header.title": "Boards", "share-board.publish": "Veröffentlichen", "share-board.share": "Teilen", + "shareBoard.channels-select-group": "Kanäle", + "shareBoard.confirm-link-channel": "Verknüpfe Board mit Kanal", + "shareBoard.confirm-link-channel-button": "Verknüpfe Kanal", + "shareBoard.confirm-link-channel-button-with-other-channel": "Verknüpfung lösen und hier verknüpfen", + "shareBoard.confirm-link-channel-subtext": "Wenn du einen Kanal mit einem Board verknüpfst, werden alle Mitglieder des Kanals (aktuelle und neue) das Board bearbeiten können.", + "shareBoard.confirm-link-channel-subtext-with-other-channel": "Wenn du einen Kanal mit einem Board verknüpfst, werden alle Mitglieder des Kanals (aktuelle und neue) dies bearbeiten können.{lineBreak}Dieses Board ist aktuell mit einem anderen Kanal verknüpft. Die Verknüpfung wird aufgehoben, wenn du es hier verknüpfst.", + "shareBoard.confirm-unlink.body": "Wenn du einen Kanal von einem Board trennst, werden alle Mitglieder des Kanals (aktuelle und neue) den Zugriff auf das Board verlieren außer die Berechtigungen wurden individuell vergeben.", + "shareBoard.confirm-unlink.confirmBtnText": "Kanal trennen", + "shareBoard.confirm-unlink.title": "Kanal vom Board trennen", "shareBoard.lastAdmin": "Boards müssen mindestens eine Administrator haben", + "shareBoard.members-select-group": "Mitglieder", "tutorial_tip.finish_tour": "Erledigt", "tutorial_tip.got_it": "Alles klar", "tutorial_tip.ok": "Weiter", diff --git a/webapp/i18n/el.json b/webapp/i18n/el.json index a68d98269..a6b2407a0 100644 --- a/webapp/i18n/el.json +++ b/webapp/i18n/el.json @@ -8,7 +8,6 @@ "CardDetail.add-content": "Προσθήκη περιεχομένου", "CardDetail.add-icon": "Προσθήκη εικονιδίου", "CardDetail.new-comment-placeholder": "Προσθήκη σχολίου ...", - "CardDialog.copyLink": "Αντιγραφή συνδέσμου", "CardDialog.nocard": "Αυτή η κάρτα δεν υπάρχει ή δεν είναι προσβάσιμη", "Comment.delete": "Διαγραφή", "CommentsList.send": "Αποστολή", @@ -27,8 +26,6 @@ "Filter.not-includes": "δεν περιέχει", "FilterComponent.add-filter": "+ Προσθήκη φίλτρου", "FilterComponent.delete": "Διαγραφή", - "GalleryCard.delete": "Διαγραφή", - "KanbanCard.delete": "Διαγραφή", "KanbanCard.untitled": "Χωρίς τίτλο", "Mutator.new-card-from-template": "νέα κάρτα από πρότυπο", "Mutator.new-template-from-card": "νέο πρότυπο από την κάρτα", diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index b2175437a..fbb5dd944 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -1,4 +1,5 @@ { + "AppBar.Tooltip": "Toggle Linked Boards", "BoardComponent.add-a-group": "+ Add a group", "BoardComponent.delete": "Delete", "BoardComponent.hidden-columns": "Hidden columns", @@ -19,10 +20,10 @@ "BoardTemplateSelector.add-template": "New template", "BoardTemplateSelector.create-empty-board": "Create empty board", "BoardTemplateSelector.delete-template": "Delete", - "BoardTemplateSelector.description": "Choose a template to help you get started. Easily customize the template to fit your needs, or create an empty board to start from scratch.", + "BoardTemplateSelector.description": "Add a board to the sidebar using any of the templates defined below or start from scratch.", "BoardTemplateSelector.edit-template": "Edit", - "BoardTemplateSelector.plugin.no-content-description": "Add a board to the sidebar using any of the templates defined below or start from scratch.{lineBreak} Members of \"{teamName}\" will have access to boards created here.", - "BoardTemplateSelector.plugin.no-content-title": "Create a Board in {teamName}", + "BoardTemplateSelector.plugin.no-content-description": "Add a board to the sidebar using any of the templates defined below or start from scratch.", + "BoardTemplateSelector.plugin.no-content-title": "Create a board", "BoardTemplateSelector.title": "Create a board", "BoardTemplateSelector.use-this-template": "Use this template", "BoardsSwitcher.Title": "Find Boards", @@ -136,10 +137,20 @@ "EditableDayPicker.today": "Today", "Error.mobileweb": "Mobile web support is currently in early beta. Not all functionality may be present.", "Error.websocket-closed": "Websocket connection closed, connection interrupted. If this persists, check your server or web proxy configuration.", + "Filter.contains": "contains", + "Filter.ends-with": "ends with", "Filter.includes": "includes", + "Filter.is": "is", "Filter.is-empty": "is empty", "Filter.is-not-empty": "is not empty", + "Filter.is-not-set": "is not set", + "Filter.is-set": "is set", + "Filter.not-contains": "doesn't contain", + "Filter.not-ends-with": "doesn't end with", "Filter.not-includes": "doesn't include", + "Filter.not-starts-with": "doesn't start with", + "Filter.starts-with": "starts with", + "FilterByText.placeholder": "filter text", "FilterComponent.add-filter": "+ Add filter", "FilterComponent.delete": "Delete", "FindBoardsDialog.IntroText": "Search for boards", @@ -150,6 +161,7 @@ "GroupBy.hideEmptyGroups": "Hide {count} empty groups", "GroupBy.showHiddenGroups": "Show {count} hidden groups", "GroupBy.ungroup": "Ungroup", + "HideBoard.MenuOption": "Hide board", "KanbanCard.untitled": "Untitled", "Mutator.new-board-from-template": "new board from template", "Mutator.new-card-from-template": "new card from template", @@ -177,16 +189,16 @@ "PropertyType.CreatedTime": "Created time", "PropertyType.Date": "Date", "PropertyType.Email": "Email", - "PropertyType.File": "File or media", "PropertyType.MultiSelect": "Multi select", "PropertyType.Number": "Number", "PropertyType.Person": "Person", "PropertyType.Phone": "Phone", "PropertyType.Select": "Select", "PropertyType.Text": "Text", - "PropertyType.URL": "URL", + "PropertyType.Unknown": "Unknown", "PropertyType.UpdatedBy": "Last updated by", "PropertyType.UpdatedTime": "Last updated time", + "PropertyType.Url": "URL", "PropertyValueElement.empty": "Empty", "RegistrationLink.confirmRegenerateToken": "This will invalidate previously shared links. Continue?", "RegistrationLink.copiedLink": "Copied!", @@ -203,7 +215,7 @@ "ShareBoard.copiedLink": "Copied!", "ShareBoard.copyLink": "Copy link", "ShareBoard.regenerate": "Regenerate token", - "ShareBoard.searchPlaceholder": "Search for people", + "ShareBoard.searchPlaceholder": "Search for people and channels", "ShareBoard.teamPermissionsText": "Everyone at {teamName} Team", "ShareBoard.tokenRegenrated": "Token regenerated", "ShareBoard.userPermissionsRemoveMemberText": "Remove member", @@ -234,6 +246,13 @@ "SidebarCategories.CategoryMenu.DeleteModal.Body": "Boards in {categoryName} will move back to the Boards categories. You're not removed from any boards.", "SidebarCategories.CategoryMenu.DeleteModal.Title": "Delete this category?", "SidebarCategories.CategoryMenu.Update": "Rename Category", + "SidebarTour.ManageCategories.Body": "Create and manage custom categories. Categories are user-specific, so moving a board to your category won’t impact other members using the same board.", + "SidebarTour.ManageCategories.Title": "Manage categories", + "SidebarTour.SearchForBoards.Body": "Open the board switcher (Cmd/Ctrl + K) to quickly search and add boards to your sidebar.", + "SidebarTour.SearchForBoards.Title": "Search for boards", + "SidebarTour.SidebarCategories.Body": "All your boards are now organized under your new sidebar. No more switching between workspaces. One-time custom categories based on your prior workspaces may have automatically been created for you as part of your v7.2 upgrade. These can be removed or edited to your preference.", + "SidebarTour.SidebarCategories.Link": "Learn more", + "SidebarTour.SidebarCategories.Title": "Sidebar categories", "TableComponent.add-icon": "Add icon", "TableComponent.name": "Name", "TableComponent.plus-new": "+ New", @@ -250,9 +269,16 @@ "URLProperty.copiedLink": "Copied!", "URLProperty.copy": "Copy", "URLProperty.edit": "Edit", + "UndoRedoHotKeys.canRedo": "Redo", + "UndoRedoHotKeys.canRedo-with-description": "Redo {description}", + "UndoRedoHotKeys.canUndo": "Undo", + "UndoRedoHotKeys.canUndo-with-description": "Undo {description}", + "UndoRedoHotKeys.cannotRedo": "Nothing to Redo", + "UndoRedoHotKeys.cannotUndo": "Nothing to Undo", "ValueSelector.noOptions": "No options. Start typing to add the first one!", "ValueSelector.valueSelector": "Value selector", "ValueSelectorLabel.openMenu": "Open menu", + "VersionMessage.help": "Check out what's new in this version.", "View.AddView": "Add view", "View.Board": "Board", "View.DeleteView": "Delete view", @@ -262,7 +288,7 @@ "View.NewCalendarTitle": "Calendar view", "View.NewGalleryTitle": "Gallery view", "View.NewTableTitle": "Table view", - "View.NewTemplateTitle": "Untitled Template", + "View.NewTemplateTitle": "Untitled", "View.Table": "Table", "ViewHeader.add-template": "New template", "ViewHeader.delete-template": "Delete", @@ -306,7 +332,8 @@ "Workspace.editing-board-template": "You're editing a board template.", "boardSelector.confirm-link-board": "Link board to channel", "boardSelector.confirm-link-board-button": "Yes, link board", - "boardSelector.confirm-link-board-subtext": "Linking the \"{boardName}\" board to this channel would give all members of this channel \"Editor\" access to the board. Are you sure you want to link it?", + "boardSelector.confirm-link-board-subtext": "When you link \"{boardName}\" to the channel, all members of the channel (existing and new) will be able to edit it. You can unlink a board from a channel at any time.", + "boardSelector.confirm-link-board-subtext-with-other-channel": "When you link \"{boardName}\" to the channel, all members of the channel (existing and new) will be able to edit it.{lineBreak} This board is currently linked to another channel. It will be unlinked if you choose to link it here.", "boardSelector.create-a-board": "Create a board", "boardSelector.link": "Link", "boardSelector.search-for-boards": "Search for boards", @@ -344,6 +371,10 @@ "register.login-button": "or log in if you already have an account", "register.signup-title": "Sign up for your account", "rhs-boards.add": "Add", + "rhs-boards.dm": "DM", + "rhs-boards.gm": "GM", + "rhs-boards.header.dm": "this Direct Message", + "rhs-boards.header.gm": "this Group Message", "rhs-boards.last-update-at": "Last update at: {datetime}", "rhs-boards.link-boards-to-channel": "Link boards to {channelName}", "rhs-boards.linked-boards": "Linked boards", @@ -351,16 +382,17 @@ "rhs-boards.no-boards-linked-to-channel-description": "Boards is a project management tool that helps define, organize, track and manage work across teams, using a familiar kanban board view.", "rhs-boards.unlink-board": "Unlink board", "rhs-channel-boards-header.title": "Boards", - "rhs-boards.dm": "DM", - "rhs-boards.header.dm": "this Direct Message", - "rhs-boards.gm": "GM", - "rhs-boards.header.gm": "this Group Message", "share-board.publish": "Publish", "share-board.share": "Share", "shareBoard.channels-select-group": "Channels", - "shareBoard.confirm-link-public-channel": "You're adding a public channel", - "shareBoard.confirm-link-public-channel-button": "Yes, add public channel", - "shareBoard.confirm-link-public-channel-subtext": "Anyone who joins that public channel will now get “Editor” access to the board, are you sure you want to proceed?", + "shareBoard.confirm-link-channel": "Link board to channel", + "shareBoard.confirm-link-channel-button": "Link channel", + "shareBoard.confirm-link-channel-button-with-other-channel": "Unlink and link here", + "shareBoard.confirm-link-channel-subtext": "When you link a channel to a board, all members of the channel (existing and new) will be able to edit it.", + "shareBoard.confirm-link-channel-subtext-with-other-channel": "When you link a channel to a board, all members of the channel (existing and new) will be able to edit it.{lineBreak}This board is currently linked to another channel. It will be unlinked if you choose to link it here.", + "shareBoard.confirm-unlink.body": "When you unlink a channel from a board, all members of the channel (existing and new) will lose access to it unless they're given permission separately.", + "shareBoard.confirm-unlink.confirmBtnText": "Unlink channel", + "shareBoard.confirm-unlink.title": "Unlink channel from board", "shareBoard.lastAdmin": "Boards must have at least one Administrator", "shareBoard.members-select-group": "Members", "tutorial_tip.finish_tour": "Done", @@ -368,4 +400,4 @@ "tutorial_tip.ok": "Next", "tutorial_tip.out": "Opt out of these tips.", "tutorial_tip.seen": "Seen this before?" -} +} \ No newline at end of file diff --git a/webapp/i18n/en_AU.json b/webapp/i18n/en_AU.json index 707006b46..80a7fd53a 100644 --- a/webapp/i18n/en_AU.json +++ b/webapp/i18n/en_AU.json @@ -8,9 +8,12 @@ "BoardComponent.no-property-title": "Items with an empty {property} property will go here. This column cannot be removed.", "BoardComponent.show": "Show", "BoardMember.schemeAdmin": "Admin", + "BoardMember.schemeCommenter": "Commenter", "BoardMember.schemeEditor": "Editor", "BoardMember.schemeNone": "None", "BoardMember.schemeViewer": "Viewer", + "BoardMember.schemeViwer": "Viewer", + "BoardMember.unlinkChannel": "Unlink", "BoardPage.newVersion": "A new version of Boards is available, click here to reload.", "BoardPage.syncFailed": "Board may be deleted or access revoked.", "BoardTemplateSelector.add-template": "New template", @@ -20,9 +23,10 @@ "BoardTemplateSelector.edit-template": "Edit", "BoardTemplateSelector.plugin.no-content-description": "Add a board to the sidebar using any of the templates defined below or start from scratch.{lineBreak} Members of '\\{teamName}'\\ will have access to boards created here.", "BoardTemplateSelector.plugin.no-content-title": "Create a Board in {teamName}", - "BoardTemplateSelector.title": "Create a Board", + "BoardTemplateSelector.title": "Create a board", "BoardTemplateSelector.use-this-template": "Use this template", "BoardsSwitcher.Title": "Find Boards", + "BoardsUnfurl.Limited": "Additional details are hidden due to the card being archived", "BoardsUnfurl.Remainder": "+{remainder} more", "BoardsUnfurl.Updated": "Updated {time}", "Calculations.Options.average.displayName": "Average", @@ -30,13 +34,13 @@ "Calculations.Options.count.displayName": "Count", "Calculations.Options.count.label": "Count", "Calculations.Options.countChecked.displayName": "Checked", - "Calculations.Options.countChecked.label": "Count Checked", + "Calculations.Options.countChecked.label": "Count checked", "Calculations.Options.countUnchecked.displayName": "Unchecked", - "Calculations.Options.countUnchecked.label": "Count Unchecked", + "Calculations.Options.countUnchecked.label": "Count unchecked", "Calculations.Options.countUniqueValue.displayName": "Unique", - "Calculations.Options.countUniqueValue.label": "Count Unique Values", + "Calculations.Options.countUniqueValue.label": "Count unique values", "Calculations.Options.countValue.displayName": "Values", - "Calculations.Options.countValue.label": "Count Value", + "Calculations.Options.countValue.label": "Count value", "Calculations.Options.dateRange.displayName": "Range", "Calculations.Options.dateRange.label": "Range", "Calculations.Options.earliest.displayName": "Earliest", @@ -52,13 +56,18 @@ "Calculations.Options.none.displayName": "Calculate", "Calculations.Options.none.label": "None", "Calculations.Options.percentChecked.displayName": "Checked", - "Calculations.Options.percentChecked.label": "Percent Checked", + "Calculations.Options.percentChecked.label": "Percent checked", "Calculations.Options.percentUnchecked.displayName": "Unchecked", - "Calculations.Options.percentUnchecked.label": "Percent Unchecked", + "Calculations.Options.percentUnchecked.label": "Percent unchecked", "Calculations.Options.range.displayName": "Range", "Calculations.Options.range.label": "Range", "Calculations.Options.sum.displayName": "Sum", "Calculations.Options.sum.label": "Sum", + "CalendarCard.untitled": "Untitled", + "CardActionsMenu.copiedLink": "Copied", + "CardActionsMenu.copyLink": "Copy link", + "CardActionsMenu.delete": "Delete", + "CardActionsMenu.duplicate": "Duplicate", "CardBadges.title-checkboxes": "Checkboxes", "CardBadges.title-comments": "Comments", "CardBadges.title-description": "This card has a description", @@ -68,20 +77,21 @@ "CardDetail.add-icon": "Add icon", "CardDetail.add-property": "+ Add a property", "CardDetail.addCardText": "add card text", - "CardDetail.moveContent": "move card content", + "CardDetail.limited-body": "Upgrade to the Professional or Enterprise plan to view archived cards, have unlimited views per boards, unlimited cards and more.", + "CardDetail.limited-button": "Upgrade", + "CardDetail.limited-title": "This card is hidden", + "CardDetail.moveContent": "Move card content", "CardDetail.new-comment-placeholder": "Add a comment", - "CardDetailProperty.confirm-delete-heading": "Confirm Property Deletion", + "CardDetailProperty.confirm-delete-heading": "Confirm property deletion", "CardDetailProperty.confirm-delete-subtext": "Are you sure you want to delete the property '{propertyName}'? This will remove the property from all cards in this board.", "CardDetailProperty.confirm-property-name-change-subtext": "Are you sure you want to change property '{propertyName}' {customText}? This will affect value(s) across {numOfCards} card(s) in this board, and may result in loss of data.", - "CardDetailProperty.confirm-property-type-change": "Confirm change of Property Type!", + "CardDetailProperty.confirm-property-type-change": "Confirm change of property type", "CardDetailProperty.delete-action-button": "Delete", - "CardDetailProperty.property-change-action-button": "Change Property", + "CardDetailProperty.property-change-action-button": "Change property", "CardDetailProperty.property-changed": "Property changed successfully!", "CardDetailProperty.property-deleted": "{propertyName} deleted successfully!", "CardDetailProperty.property-name-change-subtext": "type from '{oldPropType}' to '{newPropType}'", - "CardDetailProperty.property-type-change-subtext": "name to '{newPropName}'", - "CardDialog.copiedLink": "Copied!", - "CardDialog.copyLink": "Copy link", + "CardDetial.limited-link": "Learn more about our plans.", "CardDialog.delete-confirmation-dialog-button-text": "Delete", "CardDialog.delete-confirmation-dialog-heading": "Confirm card deletion?", "CardDialog.editing-template": "You're editing a template.", @@ -92,6 +102,7 @@ "Categories.CreateCategoryDialog.UpdateText": "Update", "CenterPanel.Login": "Login", "CenterPanel.Share": "Share", + "CloudMessage.cloud-server": "Get your own free cloud server.", "ColorOption.selectColor": "Select {color} Colour", "Comment.delete": "Delete", "CommentsList.send": "Send", @@ -118,8 +129,9 @@ "DeleteBoardDialog.confirm-cancel": "Cancel", "DeleteBoardDialog.confirm-delete": "Delete", "DeleteBoardDialog.confirm-info": "Are you sure you want to delete the board '{boardTitle}'? This will remove all cards in the board.", - "DeleteBoardDialog.confirm-tite": "Confirm Board Deletion", - "DeleteBoardDialog.confirm-tite-template": "Confirm Delete Board Template", + "DeleteBoardDialog.confirm-info-template": "Are you sure you want to delete the board template ‘{boardTitle}’?", + "DeleteBoardDialog.confirm-tite": "Confirm board deletion", + "DeleteBoardDialog.confirm-tite-template": "Confirm deletion of board template", "Dialog.closeDialog": "Close dialog", "EditableDayPicker.today": "Today", "Error.mobileweb": "Mobile web support is currently in early beta. Not all functionality may be present.", @@ -130,23 +142,16 @@ "Filter.not-includes": "doesn't include", "FilterComponent.add-filter": "+ Add filter", "FilterComponent.delete": "Delete", - "FindBoFindBoardsDialog.IntroText": "Search for boards", + "FindBoardsDialog.IntroText": "Search for boards", "FindBoardsDialog.NoResultsFor": "No results for '\\{searchQuery}'\\", "FindBoardsDialog.NoResultsSubtext": "Check the spelling or try another search.", "FindBoardsDialog.SubTitle": "Type to find a board. Use UP/DOWN to browse. ENTER to select, ESC to dismiss", "FindBoardsDialog.Title": "Find Boards", - "GalleryCard.copiedLink": "Copied!", - "GalleryCard.copyLink": "Copy link", - "GalleryCard.delete": "Delete", - "GalleryCard.duplicate": "Duplicate", "GroupBy.hideEmptyGroups": "Hide {count} empty groups", "GroupBy.showHiddenGroups": "Show {count} hidden groups", "GroupBy.ungroup": "Ungroup", - "KanbanCard.copiedLink": "Copied!", - "KanbanCard.copyLink": "Copy link", - "KanbanCard.delete": "Delete", - "KanbanCard.duplicate": "Duplicate", "KanbanCard.untitled": "Untitled", + "Mutator.new-board-from-template": "new board from template", "Mutator.new-card-from-template": "new card from template", "Mutator.new-template-from-card": "new template from card", "OnboardingTour.AddComments.Body": "You can comment on issues, and even @mention your fellow Mattermost users to get their attention.", @@ -157,7 +162,7 @@ "OnboardingTour.AddProperties.Title": "Add properties", "OnboardingTour.AddView.Body": "Go here to create a new view to organise your board using different layouts.", "OnboardingTour.AddView.Title": "Add a new view", - "OnboardingTour.CopyLink.Body": "You can share your cards with teammates by copying the link and pasting it in a channel, Direct Message or Group Message.", + "OnboardingTour.CopyLink.Body": "You can share your cards with teammates by copying the link and pasting it in a channel, direct message or group message.", "OnboardingTour.CopyLink.Title": "Copy link", "OnboardingTour.OpenACard.Body": "Open a card to explore the powerful ways that Boards can help you organise your work.", "OnboardingTour.OpenACard.Title": "Open a card", @@ -172,8 +177,8 @@ "PropertyType.CreatedTime": "Time created", "PropertyType.Date": "Date", "PropertyType.Email": "Email", - "PropertyType.File": "File or Media", - "PropertyType.MultiSelect": "Multi Select", + "PropertyType.File": "File or media", + "PropertyType.MultiSelect": "Multi select", "PropertyType.Number": "Number", "PropertyType.Person": "Person", "PropertyType.Phone": "Phone", @@ -189,15 +194,16 @@ "RegistrationLink.description": "Share this link for others to create accounts:", "RegistrationLink.regenerateToken": "Regenerate token", "RegistrationLink.tokenRegenerated": "Registration link regenerated", - "ShareBoard.PublishDescription": "Publish and share a 'read only' link with everyone on the web", + "ShareBoard.PublishDescription": "Publish and share a read-only link with everyone on the web.", "ShareBoard.PublishTitle": "Publish to the web", "ShareBoard.ShareInternal": "Share internally", - "ShareBoard.ShareInternalDescription": "Users who have permissions will be able to use this link", + "ShareBoard.ShareInternalDescription": "Users who have permissions will be able to use this link.", "ShareBoard.Title": "Share Board", "ShareBoard.confirmRegenerateToken": "This will invalidate previously shared links. Continue?", "ShareBoard.copiedLink": "Copied!", "ShareBoard.copyLink": "Copy link", "ShareBoard.regenerate": "Regenerate token", + "ShareBoard.searchPlaceholder": "Search for people and channels", "ShareBoard.teamPermissionsText": "Everyone at {teamName} Team", "ShareBoard.tokenRegenrated": "Token regenerated", "ShareBoard.userPermissionsRemoveMemberText": "Remove member", @@ -240,7 +246,7 @@ "TableHeaderMenu.sort-descending": "Sort descending", "TableRow.delete": "Delete", "TableRow.open": "Open", - "TopBar.give-feedback": "Give Feedback", + "TopBar.give-feedback": "Give feedback", "URLProperty.copiedLink": "Copied", "URLProperty.copy": "Copy", "URLProperty.edit": "Edit", @@ -256,6 +262,7 @@ "View.NewCalendarTitle": "Calendar view", "View.NewGalleryTitle": "Gallery view", "View.NewTableTitle": "Table view", + "View.NewTemplateTitle": "Untitled Template", "View.Table": "Table", "ViewHeader.add-template": "New template", "ViewHeader.delete-template": "Delete", @@ -271,7 +278,6 @@ "ViewHeader.new": "New", "ViewHeader.properties": "Properties", "ViewHeader.properties-menu": "Properties menu", - "ViewHeader.search": "Search", "ViewHeader.search-text": "Search cards", "ViewHeader.select-a-template": "Select a template", "ViewHeader.set-default-template": "Set as default", @@ -279,34 +285,84 @@ "ViewHeader.untitled": "Untitled", "ViewHeader.view-header-menu": "View header menu", "ViewHeader.view-menu": "View menu", + "ViewLimitDialog.Heading": "Views per board limit reached", + "ViewLimitDialog.PrimaryButton.Title.Admin": "Upgrade", + "ViewLimitDialog.PrimaryButton.Title.RegularUser": "Notify Admin", + "ViewLimitDialog.Subtext.Admin": "Upgrade to the Professional or Enterprise plan to have unlimited views per boards, unlimited cards and more.", + "ViewLimitDialog.Subtext.Admin.PricingPageLink": "Learn more about our plans.", + "ViewLimitDialog.Subtext.RegularUser": "Ask your Admin to upgrade to the Professional or Enterprise plan to have unlimited views per boards, unlimited cards and more.", + "ViewLimitDialog.UpgradeImg.AltText": "upgrade image", + "ViewLimitDialog.notifyAdmin.Success": "Your admin has been contacted", "ViewTitle.hide-description": "hide description", "ViewTitle.pick-icon": "Pick icon", "ViewTitle.random-icon": "Random", "ViewTitle.remove-icon": "Remove icon", "ViewTitle.show-description": "show description", - "ViewTitle.untitled-board": "Untitled Board", + "ViewTitle.untitled-board": "Untitled board", "WelcomePage.Description": "Boards is a project management tool that helps define, organise, track and manage work across teams using a familiar Kanban board view.", "WelcomePage.Explore.Button": "Take a tour", "WelcomePage.Heading": "Welcome To Boards", "WelcomePage.NoThanks.Text": "No thanks, I'll figure it out myself", "Workspace.editing-board-template": "You're editing a board template.", + "boardSelector.confirm-link-board": "Link board to channel", + "boardSelector.confirm-link-board-button": "Link board", + "boardSelector.confirm-link-board-subtext-with-other-channel": "When you link \"{boardName}\" to the channel, all members of the channel (existing and new) will be able to edit it.{lineBreak} This board is currently linked to another channel. It will be unlinked if you choose to link it here.", + "boardSelector.create-a-board": "Create a board", + "boardSelector.link": "Link", + "boardSelector.search-for-boards": "Search for boards", + "boardSelector.title": "Link boards", + "boardSelector.unlink": "Unlink", "calendar.month": "Month", "calendar.today": "TODAY", "calendar.week": "Week", + "cloudMessage.learn-more": "Learn more", "createImageBlock.failed": "Unable to upload the file. Individual file size limit exceeded.", - "default-properties.badges": "Comments and Description", + "default-properties.badges": "Comments and description", "default-properties.title": "Title", + "error.back-to-home": "Back to Home", + "error.back-to-team": "Back to team", + "error.board-not-found": "Board not found.", + "error.go-login": "Log in", + "error.invalid-read-only-board": "You don’t have access to this board. Log in to access Boards.", + "error.not-logged-in": "Your session may have expired or you're not logged in. Log in again to access Boards.", "error.page.title": "An error occurred", + "error.team-undefined": "Invalid team.", + "error.unknown": "An error occurred.", "generic.previous": "Previous", "imagePaste.upload-failed": "Some files were not uploaded because they exceeded the individual file size limit.", + "limitedCard.title": "Cards hidden", "login.log-in-button": "Log in", "login.log-in-title": "Log in", "login.register-button": "or create an account if you don't have one", + "notification-box-card-limit-reached.close-tooltip": "Snooze for 10 days", + "notification-box-card-limit-reached.contact-link": "Contact your adminstrator", + "notification-box-card-limit-reached.link": "Upgrade to a paid plan", + "notification-box-card-limit-reached.title": "{cards} cards hidden from board", + "notification-box-cards-hidden.title": "This action has hidden another card", + "notification-box.card-limit-reached.not-admin.text": "To access archived cards, you can {contactLink} to upgrade to a paid plan.", + "notification-box.card-limit-reached.text": "Card limit reached, to view older cards, {link}", "register.login-button": "or log in if you already have an account", "register.signup-title": "Sign up for your account", + "rhs-boards.add": "Add", + "rhs-boards.dm": "DM", + "rhs-boards.gm": "GM", + "rhs-boards.header.dm": "this Direct Message", + "rhs-boards.header.gm": "this Group Message", + "rhs-boards.last-update-at": "Last update at: {datetime}", + "rhs-boards.link-boards-to-channel": "Link boards to {channelName}", + "rhs-boards.linked-boards": "Linked boards", + "rhs-boards.no-boards-linked-to-channel": "No boards are linked to {channelName} yet", + "rhs-boards.no-boards-linked-to-channel-description": "Boards is a project management tool that helps define, organise, track and manage work across teams using a familiar kanban board view.", + "rhs-boards.unlink-board": "Unlink board", + "rhs-channel-boards-header.title": "Boards", "share-board.publish": "Publish", "share-board.share": "Share", + "shareBoard.channels-select-group": "Channels", + "shareBoard.confirm-link-public-channel": "You're adding a public channel", + "shareBoard.confirm-link-public-channel-button": "Yes, add public channel", + "shareBoard.confirm-link-public-channel-subtext": "Anyone who joins that public channel will now get 'Editor' access to the board. Are you sure you want to proceed?", "shareBoard.lastAdmin": "Boards must have at least one Administrator", + "shareBoard.members-select-group": "Members", "tutorial_tip.finish_tour": "Done", "tutorial_tip.got_it": "Got it", "tutorial_tip.ok": "Next", diff --git a/webapp/i18n/es.json b/webapp/i18n/es.json index 1cca0cc24..f35fdbebc 100644 --- a/webapp/i18n/es.json +++ b/webapp/i18n/es.json @@ -43,8 +43,6 @@ "CardDetail.new-comment-placeholder": "Añadir un comentario...", "CardDetailProperty.confirm-delete-subtext": "¿Estas seguro de que quieres eliminar la propiedad \"{nombre de la propiedad}\"? Al eliminarla se borrará la propiedad de todas las tarjetas de este tablero.", "CardDetailProperty.property-deleted": "¡{nombre de la propiedad} ha sido eliminado exitosamente!", - "CardDialog.copiedLink": "¡Copiado!", - "CardDialog.copyLink": "Copiar enlace", "CardDialog.editing-template": "Estás editando una plantilla.", "CardDialog.nocard": "Esta tarjeta no existe o es inaccesible.", "ColorOption.selectColor": "Seleccionar {color} Color", @@ -73,15 +71,7 @@ "Filter.not-includes": "no incluye", "FilterComponent.add-filter": "+ Añadir filtro", "FilterComponent.delete": "Borrar", - "GalleryCard.copiedLink": "¡Copiado!", - "GalleryCard.copyLink": "Copiar enlace", - "GalleryCard.delete": "Borrar", - "GalleryCard.duplicate": "Duplicar", "GroupBy.ungroup": "Desagrupar", - "KanbanCard.copiedLink": "¡Copiado!", - "KanbanCard.copyLink": "Copiar enlace", - "KanbanCard.delete": "Eliminar", - "KanbanCard.duplicate": "Duplicar", "KanbanCard.untitled": "Sin título", "Mutator.new-card-from-template": "nueva tarjeta desde una plantilla", "Mutator.new-template-from-card": "nueva plantilla desde una tarjeta", @@ -160,7 +150,6 @@ "ViewHeader.group-by": "Agrupar por: {property}", "ViewHeader.new": "Nueva", "ViewHeader.properties": "Propiedades", - "ViewHeader.search": "Buscar", "ViewHeader.search-text": "Texto de búsqueda", "ViewHeader.select-a-template": "Seleccionar una plantilla", "ViewHeader.sort": "Ordenar", diff --git a/webapp/i18n/et.json b/webapp/i18n/et.json index 0daae4928..367f57c81 100644 --- a/webapp/i18n/et.json +++ b/webapp/i18n/et.json @@ -40,8 +40,6 @@ "CardDetailProperty.confirm-delete-heading": "Kinnita omaduse kustutamine", "CardDetailProperty.delete-action-button": "Kustuta", "CardDetailProperty.property-change-action-button": "Muuda omadust", - "CardDialog.copiedLink": "Kopeeritud!", - "CardDialog.copyLink": "Kopeeri link", "CardDialog.editing-template": "Sa muudad malli.", "CardDialog.nocard": "Seda kaarti pole olemas või see pole ligipääsetav.", "Comment.delete": "Kustuta", @@ -69,14 +67,6 @@ "Filter.not-includes": "ei sisalda", "FilterComponent.add-filter": "+ Lisa filter", "FilterComponent.delete": "Kustuta", - "GalleryCard.copiedLink": "Kopeeritud!", - "GalleryCard.copyLink": "Kopeeri link", - "GalleryCard.delete": "Kustuta", - "GalleryCard.duplicate": "Tee koopia", - "KanbanCard.copiedLink": "Kopeeritud!", - "KanbanCard.copyLink": "Kopeeri link", - "KanbanCard.delete": "Kustuta", - "KanbanCard.duplicate": "Tee koopia", "KanbanCard.untitled": "Nimetu", "Mutator.new-card-from-template": "malli põhjal uus kaart", "PropertyMenu.Delete": "Kustuta", @@ -141,7 +131,6 @@ "ViewHeader.group-by": "Grupeeri: {property}", "ViewHeader.new": "Uus", "ViewHeader.properties": "Omadused", - "ViewHeader.search": "Otsi", "ViewHeader.search-text": "Otsi teksti", "ViewHeader.select-a-template": "Vali mall", "ViewHeader.set-default-template": "Määra vaikeväärtuseks", diff --git a/webapp/i18n/fa.json b/webapp/i18n/fa.json index c1b2b885b..4be4b23a8 100644 --- a/webapp/i18n/fa.json +++ b/webapp/i18n/fa.json @@ -79,9 +79,6 @@ "CardDetailProperty.property-changed": "تغییر ویژگی با موفقیت!", "CardDetailProperty.property-deleted": "{propertyName} با موفقیت حذف شد!", "CardDetailProperty.property-name-change-subtext": "از \"{oldPropType}\" به \"{newPropType}\" تایپ کنید", - "CardDetailProperty.property-type-change-subtext": "نام برای \"{newPropName}\"", - "CardDialog.copiedLink": "کپی شده!", - "CardDialog.copyLink": "لینک را کپی کنید", "CardDialog.delete-confirmation-dialog-button-text": "حذف", "CardDialog.delete-confirmation-dialog-heading": "حذف کارت را تایید کنید!", "CardDialog.editing-template": "شما در حال ویرایش یک الگو هستید.", diff --git a/webapp/i18n/fr.json b/webapp/i18n/fr.json index 573b1d312..5e2864b12 100644 --- a/webapp/i18n/fr.json +++ b/webapp/i18n/fr.json @@ -8,9 +8,12 @@ "BoardComponent.no-property-title": "Les éléments sans propriété {property} seront placés ici. Cette colonne ne peut pas être supprimée.", "BoardComponent.show": "Montrer", "BoardMember.schemeAdmin": "Admin", + "BoardMember.schemeCommenter": "Commentateur", "BoardMember.schemeEditor": "Éditeur", "BoardMember.schemeNone": "Aucun", "BoardMember.schemeViewer": "Lecteur", + "BoardMember.schemeViwer": "Lecteur", + "BoardMember.unlinkChannel": "Détacher", "BoardPage.newVersion": "Une nouvelle version de Boards est disponible, cliquez ici pour recharger.", "BoardPage.syncFailed": "Le tableau a peut-être été supprimé ou vos droits d'accès révoqués.", "BoardTemplateSelector.add-template": "Nouveau modèle", @@ -23,6 +26,7 @@ "BoardTemplateSelector.title": "Créer un tableau", "BoardTemplateSelector.use-this-template": "Utiliser ce modèle", "BoardsSwitcher.Title": "Rechercher des tableaux", + "BoardsUnfurl.Limited": "Les détails supplémentaires sont masqués car la carte est archivée", "BoardsUnfurl.Remainder": "+{remainder} plus", "BoardsUnfurl.Updated": "Mis à jour {time}", "Calculations.Options.average.displayName": "Moyenne", @@ -59,6 +63,11 @@ "Calculations.Options.range.label": "Curseur", "Calculations.Options.sum.displayName": "Somme", "Calculations.Options.sum.label": "Somme", + "CalendarCard.untitled": "Sans titre", + "CardActionsMenu.copiedLink": "Copié !", + "CardActionsMenu.copyLink": "Copier le lien", + "CardActionsMenu.delete": "Supprimer", + "CardActionsMenu.duplicate": "Dupliquer", "CardBadges.title-checkboxes": "Cases à cocher", "CardBadges.title-comments": "Commentaires", "CardBadges.title-description": "Cette carte a une description", @@ -68,20 +77,21 @@ "CardDetail.add-icon": "Ajouter une icône", "CardDetail.add-property": "+ Ajouter une propriété", "CardDetail.addCardText": "ajouter une carte texte", - "CardDetail.moveContent": "déplacer le contenu de la carte", + "CardDetail.limited-body": "Passez à notre offre Professionnel ou Entreprise pour afficher les cartes archivées, avoir des vues illimitées par tableau, des cartes illimitées et plus encore.", + "CardDetail.limited-button": "Mise à niveau", + "CardDetail.limited-title": "Cette carte est masquée", + "CardDetail.moveContent": "Déplacer le contenu de la carte", "CardDetail.new-comment-placeholder": "Ajouter un commentaire...", "CardDetailProperty.confirm-delete-heading": "Confirmer la suppression de la propriété", "CardDetailProperty.confirm-delete-subtext": "Êtes-vous sûr de vouloir supprimer la propriété « {propertyName} » ? La suppression retirera la propriété de toutes les cartes dans ce tableau.", "CardDetailProperty.confirm-property-name-change-subtext": "Voulez-vous vraiment modifier le type de la propriété \"{propertyName}\" {customText} ? Cela affectera la ou les valeur(s) sur {numOfCards} carte(s) dans ce tableau et peut entraîner une perte de données.", - "CardDetailProperty.confirm-property-type-change": "Confirmer le changement de type de propriété !", + "CardDetailProperty.confirm-property-type-change": "Confirmer le changement de type de propriété", "CardDetailProperty.delete-action-button": "Supprimer", "CardDetailProperty.property-change-action-button": "Modifier la propriété", "CardDetailProperty.property-changed": "Propriété modifiée avec succès !", "CardDetailProperty.property-deleted": "{propertyName} supprimé avec succès !", "CardDetailProperty.property-name-change-subtext": "de \"{oldPropType}\" à \"{newPropType}\"", - "CardDetailProperty.property-type-change-subtext": "nom de \"{newPropName}\"", - "CardDialog.copiedLink": "Copié !", - "CardDialog.copyLink": "Copier le lien", + "CardDetial.limited-link": "En savoir plus sur nos offres.", "CardDialog.delete-confirmation-dialog-button-text": "Supprimer", "CardDialog.delete-confirmation-dialog-heading": "Confirmer la suppression de la carte !", "CardDialog.editing-template": "Vous éditez un modèle.", @@ -92,6 +102,7 @@ "Categories.CreateCategoryDialog.UpdateText": "Mettre à jour", "CenterPanel.Login": "Connexion", "CenterPanel.Share": "Partager", + "CloudMessage.cloud-server": "Obtenez votre propre serveur cloud gratuitement.", "ColorOption.selectColor": "Choisir la couleur {color}", "Comment.delete": "Supprimer", "CommentsList.send": "Envoyer", @@ -118,6 +129,7 @@ "DeleteBoardDialog.confirm-cancel": "Annuler", "DeleteBoardDialog.confirm-delete": "Supprimer", "DeleteBoardDialog.confirm-info": "Êtes-vous sûr de vouloir supprimer le tableau «{boardTitle}» ? Cela supprimera toutes les cartes dans ce tableau.", + "DeleteBoardDialog.confirm-info-template": "Voulez-vous vraiment supprimer le modèle de tableau “{boardTitle}”?", "DeleteBoardDialog.confirm-tite": "Confirmer la suppression du tableau", "DeleteBoardDialog.confirm-tite-template": "Confirmer la suppression du modèle", "Dialog.closeDialog": "Fermer la boîte de dialogue", @@ -130,23 +142,16 @@ "Filter.not-includes": "n'inclut pas", "FilterComponent.add-filter": "+ Ajouter un filtre", "FilterComponent.delete": "Supprimer", - "FindBoFindBoardsDialog.IntroText": "Rechercher des tableaux", + "FindBoardsDialog.IntroText": "Rechercher des tableaux", "FindBoardsDialog.NoResultsFor": "Pas de résultats pour \"{searchQuery}\"", "FindBoardsDialog.NoResultsSubtext": "Vérifiez l'orthographe ou essayez une autre recherche.", "FindBoardsDialog.SubTitle": "Recherchez ci-dessous pour trouver un tableau. Utilisez HAUT/BAS pour naviguer. ENTRER pour sélectionner, ECHAP pour annuler", "FindBoardsDialog.Title": "Rechercher des tableaux", - "GalleryCard.copiedLink": "Copié !", - "GalleryCard.copyLink": "Copier le lien", - "GalleryCard.delete": "Supprimer", - "GalleryCard.duplicate": "Dupliquer", "GroupBy.hideEmptyGroups": "Masquer {count} groupes vides", "GroupBy.showHiddenGroups": "Afficher les {count} groupes masqués", "GroupBy.ungroup": "Dégrouper", - "KanbanCard.copiedLink": "Copié !", - "KanbanCard.copyLink": "Copier le lien", - "KanbanCard.delete": "Supprimer", - "KanbanCard.duplicate": "Dupliquer", "KanbanCard.untitled": "Sans titre", + "Mutator.new-board-from-template": "nouveau tableau à partir du modèle", "Mutator.new-card-from-template": "nouvelle carte depuis un modèle", "Mutator.new-template-from-card": "nouveau modèle depuis une carte", "OnboardingTour.AddComments.Body": "Vous pouvez commenter les bugs et même @mentionner les utilisateurs de Mattermost pour attirer leur attention.", @@ -157,7 +162,7 @@ "OnboardingTour.AddProperties.Title": "Ajouter des propriétés", "OnboardingTour.AddView.Body": "Allez ici pour créer une nouvelle vue pour organiser votre tableau en utilisant différentes mises en page.", "OnboardingTour.AddView.Title": "Ajouter une nouvelle vue", - "OnboardingTour.CopyLink.Body": "Vous pouvez partager vos cartes avec votre équipe en copiant le lien et en le collant dans un canal, un message direct ou un groupe.", + "OnboardingTour.CopyLink.Body": "Vous pouvez partager vos cartes avec votre équipe en copiant le lien et en le collant dans un canal, un message direct ou un message de groupe.", "OnboardingTour.CopyLink.Title": "Copier le lien", "OnboardingTour.OpenACard.Body": "Ouvrir une carte pour découvrir les façons dont les tableaux peuvent vous aider à organiser votre travail.", "OnboardingTour.OpenACard.Title": "Ouvrir une carte", @@ -192,12 +197,13 @@ "ShareBoard.PublishDescription": "Publier et partager un lien « en lecture seule » avec tout le monde sur le web", "ShareBoard.PublishTitle": "Publier sur le web", "ShareBoard.ShareInternal": "Partager en interne", - "ShareBoard.ShareInternalDescription": "Les utilisateurs qui ont des autorisations pourront utiliser ce lien", + "ShareBoard.ShareInternalDescription": "Les utilisateurs qui ont des autorisations pourront utiliser ce lien.", "ShareBoard.Title": "Partager le tableau", "ShareBoard.confirmRegenerateToken": "Ceci va désactiver les liens de partages existants. Continuer ?", "ShareBoard.copiedLink": "Copié !", "ShareBoard.copyLink": "Copier le lien", "ShareBoard.regenerate": "Régénérer le jeton", + "ShareBoard.searchPlaceholder": "Rechercher des membres", "ShareBoard.teamPermissionsText": "Tout le monde à l'équipe {teamName}", "ShareBoard.tokenRegenrated": "Le jeton a été recréé", "ShareBoard.userPermissionsRemoveMemberText": "Supprimer un membre", @@ -238,6 +244,7 @@ "TableHeaderMenu.insert-right": "Insérer à droite", "TableHeaderMenu.sort-ascending": "Tri ascendant", "TableHeaderMenu.sort-descending": "Tri descendant", + "TableRow.delete": "Supprimer", "TableRow.open": "Ouvrir", "TopBar.give-feedback": "Donner un avis", "URLProperty.copiedLink": "Copié !", @@ -255,6 +262,7 @@ "View.NewCalendarTitle": "Vue calendrier", "View.NewGalleryTitle": "Vue en galerie", "View.NewTableTitle": "Vue en table", + "View.NewTemplateTitle": "Modèle sans titre", "View.Table": "Table", "ViewHeader.add-template": "Nouveau modèle", "ViewHeader.delete-template": "Supprimer", @@ -270,42 +278,88 @@ "ViewHeader.new": "Nouveau", "ViewHeader.properties": "Propriétés", "ViewHeader.properties-menu": "Menu propriétés", - "ViewHeader.search": "Rechercher", - "ViewHeader.search-text": "Rechercher du texte", + "ViewHeader.search-text": "Rechercher des cartes", "ViewHeader.select-a-template": "Sélectionner un modèle", "ViewHeader.set-default-template": "Définir par défaut", "ViewHeader.sort": "Trier", "ViewHeader.untitled": "Sans titre", "ViewHeader.view-header-menu": "Afficher le menu d'en-tête", "ViewHeader.view-menu": "Afficher le menu", + "ViewLimitDialog.Heading": "Limite de vues par tableau atteinte", + "ViewLimitDialog.PrimaryButton.Title.Admin": "Mise à niveau", + "ViewLimitDialog.PrimaryButton.Title.RegularUser": "Notifier l'Admin", + "ViewLimitDialog.Subtext.Admin": "Passez à notre offre Professionnel ou Entreprise pour afficher les cartes archivées, avoir des vues illimitées par tableau, des cartes illimitées et plus encore.", + "ViewLimitDialog.Subtext.Admin.PricingPageLink": "En savoir plus sur nos offres.", + "ViewLimitDialog.Subtext.RegularUser": "Informez votre administrateur qu'il peut passer à notre offre professionnel ou d'entreprise pour avoir un nombre illimité de vues par tableau, un nombre illimité de cartes, et plus encore.", + "ViewLimitDialog.UpgradeImg.AltText": "mise à jour de l'image", + "ViewLimitDialog.notifyAdmin.Success": "Votre administrateur a été notifié", "ViewTitle.hide-description": "cacher la description", "ViewTitle.pick-icon": "Choisir une icône", "ViewTitle.random-icon": "Aléatoire", "ViewTitle.remove-icon": "Supprimer l'icône", "ViewTitle.show-description": "montrer la description", "ViewTitle.untitled-board": "Tableau sans titre", - "WelcomePage.Description": "Boards est un outil de gestion de projet qui permet d'organiser, de suivre et de gérer le travail entre équipes, utilisant des tableaux Kanban", + "WelcomePage.Description": "Boards est un outil de gestion de projet qui permet d'organiser, de suivre et de gérer le travail entre équipes en utilisant des tableaux Kanban.", "WelcomePage.Explore.Button": "Tutoriel", "WelcomePage.Heading": "Bienvenue sur Boards", "WelcomePage.NoThanks.Text": "Non merci, je vais me renseigner moi-même", "Workspace.editing-board-template": "Vous éditez un modèle de tableau.", + "boardSelector.confirm-link-board": "Lier la carte au canal", + "boardSelector.confirm-link-board-button": "Oui, lier ce tableau", + "boardSelector.confirm-link-board-subtext": "Lier le tableau \"{boardName}\" à ce canal donnerait à tous les membres de ce canal, l'accès \"Editeur\" à ce tableau. Êtes-vous sûr de vouloir le lier ?", + "boardSelector.create-a-board": "Créer un tableau", + "boardSelector.link": "Lien", + "boardSelector.search-for-boards": "Rechercher des tableaux", + "boardSelector.title": "Lier les tableaux", + "boardSelector.unlink": "Détacher", "calendar.month": "Mois", "calendar.today": "AUJOURD'HUI", "calendar.week": "Semaine", + "cloudMessage.learn-more": "En savoir plus", "createImageBlock.failed": "Impossible de télécharger le fichier. Limite de taille de fichier atteinte.", "default-properties.badges": "Commentaires et description", "default-properties.title": "Titre", + "error.back-to-home": "Retour à la page d'accueil", + "error.back-to-team": "Retour à l'équipe", + "error.board-not-found": "Tableau non trouvé.", + "error.go-login": "Connexion", + "error.invalid-read-only-board": "Vous n'avez pas accès à ce tableau. Connectez-vous pour y accéder.", + "error.not-logged-in": "Votre session a peut-être expiré ou vous n’êtes pas connecté. Connectez-vous à nouveau pour accéder aux tableaux d'administration.", "error.page.title": "Désolé, quelque chose s'est mal passé", + "error.team-undefined": "Ce n'est pas une équipe valable.", + "error.unknown": "Une erreur s'est produite.", "generic.previous": "Précédent", "imagePaste.upload-failed": "Certains fichiers n'ont pas été téléchargés. Limite de taille de fichier atteinte", + "limitedCard.title": "Cartes cachées", "login.log-in-button": "Connexion", "login.log-in-title": "Connexion", "login.register-button": "ou créez un compte si vous n'en avez pas", + "notification-box-card-limit-reached.close-tooltip": "Oublier pendant 10 jours", + "notification-box-card-limit-reached.contact-link": "informez votre administrateur", + "notification-box-card-limit-reached.link": "Passer à une offre payante", + "notification-box-card-limit-reached.title": "{cards} cartes cachées du tableau", + "notification-box-cards-hidden.title": "Cette action a caché une autre carte", + "notification-box.card-limit-reached.not-admin.text": "Pour accéder aux cartes archivées, vous pouvez {contactLink} pour passer à une offre payante.", + "notification-box.card-limit-reached.text": "Limite de carte atteinte, pour visualiser les anciennes cartes, {link}.", "register.login-button": "ou connectez-vous si vous avez déjà un compte", "register.signup-title": "Inscrivez-vous pour créer un compte", + "rhs-boards.add": "Ajouter", + "rhs-boards.dm": "DM", + "rhs-boards.gm": "GM", + "rhs-boards.header.dm": "ce message direct", + "rhs-boards.header.gm": "ce message de groupe", + "rhs-boards.last-update-at": "Dernière mise à jour à : {datetime}", + "rhs-boards.link-boards-to-channel": "Lier les tableaux à {channelName}", + "rhs-boards.linked-boards": "Tableaux liés", + "rhs-boards.no-boards-linked-to-channel": "Aucun tableau n'est lié à {channelName} pour le moment", + "rhs-boards.no-boards-linked-to-channel-description": "Boards est un outil de gestion de projet qui permet d'organiser, de suivre et de gérer le travail entre équipes en utilisant des tableaux Kanban.", + "rhs-boards.unlink-board": "Dissocier le tableau", + "rhs-channel-boards-header.title": "Tableaux", "share-board.publish": "Publier", "share-board.share": "Partager", + "shareBoard.channels-select-group": "Canaux", "shareBoard.lastAdmin": "Les conseils doivent avoir au moins un administrateur", + "shareBoard.members-select-group": "Membres", "tutorial_tip.finish_tour": "Terminé", "tutorial_tip.got_it": "J'ai compris", "tutorial_tip.ok": "Suivant", diff --git a/webapp/i18n/hr.json b/webapp/i18n/hr.json index a51791fc9..7ff99ecf4 100644 --- a/webapp/i18n/hr.json +++ b/webapp/i18n/hr.json @@ -8,9 +8,12 @@ "BoardComponent.no-property-title": "Elementi s praznim svojstvom {property} smjestit će se ovdje. Ovaj se stupac ne može ukloniti.", "BoardComponent.show": "Prikaži", "BoardMember.schemeAdmin": "Administrator", + "BoardMember.schemeCommenter": "Komentator", "BoardMember.schemeEditor": "Urednik", "BoardMember.schemeNone": "Ništa", "BoardMember.schemeViewer": "Gledatelj", + "BoardMember.schemeViwer": "Gledatelj", + "BoardMember.unlinkChannel": "Ukloni poveznicu", "BoardPage.newVersion": "Dostupna je nova verzija za „Ploče”. Pritisni ovdje za ponovno učitavanje.", "BoardPage.syncFailed": "Ploča se može izbrisati ili pristup opozvati.", "BoardTemplateSelector.add-template": "Novi predložak", @@ -23,6 +26,7 @@ "BoardTemplateSelector.title": "Stvori ploču", "BoardTemplateSelector.use-this-template": "Koristi ovaj predložak", "BoardsSwitcher.Title": "Pronađi ploče", + "BoardsUnfurl.Limited": "Dodatni detalji su skriveni jer je kartica arhivirana", "BoardsUnfurl.Remainder": "+ još {remainder}", "BoardsUnfurl.Updated": "Aktulaizirano {time}", "Calculations.Options.average.displayName": "Prosjek", @@ -37,12 +41,12 @@ "Calculations.Options.countUniqueValue.label": "Broji jedinstvene vrijednosti", "Calculations.Options.countValue.displayName": "Vrijednosti", "Calculations.Options.countValue.label": "Vrijednost zbroja", - "Calculations.Options.dateRange.displayName": "Opseg", - "Calculations.Options.dateRange.label": "Opseg", + "Calculations.Options.dateRange.displayName": "Raspon", + "Calculations.Options.dateRange.label": "Raspon", "Calculations.Options.earliest.displayName": "Najraniji", "Calculations.Options.earliest.label": "Najraniji", - "Calculations.Options.latest.displayName": "Najkasniji", - "Calculations.Options.latest.label": "Najkasniji", + "Calculations.Options.latest.displayName": "Najnoviji", + "Calculations.Options.latest.label": "Najnoviji", "Calculations.Options.max.displayName": "Maks.", "Calculations.Options.max.label": "Maks.", "Calculations.Options.median.displayName": "Medijan", @@ -55,10 +59,15 @@ "Calculations.Options.percentChecked.label": "Provjeren postotak", "Calculations.Options.percentUnchecked.displayName": "Neprovjereno", "Calculations.Options.percentUnchecked.label": "Neprovjeren postotak", - "Calculations.Options.range.displayName": "Opseg", - "Calculations.Options.range.label": "Opseg", + "Calculations.Options.range.displayName": "Raspon", + "Calculations.Options.range.label": "Raspon", "Calculations.Options.sum.displayName": "Zbroj", "Calculations.Options.sum.label": "Zbroj", + "CalendarCard.untitled": "Bez naslova", + "CardActionsMenu.copiedLink": "Kopirano!", + "CardActionsMenu.copyLink": "Kopiraj poveznicu", + "CardActionsMenu.delete": "Izbriši", + "CardActionsMenu.duplicate": "Dupliciraj", "CardBadges.title-checkboxes": "Označiva polja", "CardBadges.title-comments": "Komentari", "CardBadges.title-description": "Ova kartica ima opis", @@ -68,30 +77,32 @@ "CardDetail.add-icon": "Dodaj ikonu", "CardDetail.add-property": "+ Dodaj svojstvo", "CardDetail.addCardText": "dodaj tekst kartice", - "CardDetail.moveContent": "pomakni sadržaj kartice", + "CardDetail.limited-body": "Nadogradi na našu profesionalnu tarifu ili na tarifu za prikaz arhiviranih kartica. Dobij neograničeni broj prikaza po ploči, neograničen broj kartica i još mnogo toga.", + "CardDetail.limited-button": "Nadogradi", + "CardDetail.limited-title": "Ova je kartica skrivena", + "CardDetail.moveContent": "Pomakni sadržaj kartice", "CardDetail.new-comment-placeholder": "Dodaj komentar …", "CardDetailProperty.confirm-delete-heading": "Potvrdi brisanje svojstva", "CardDetailProperty.confirm-delete-subtext": "Stvarno želiš izbrisati svojstvo „{propertyName}”? Brisanjem će se izbrisati svojstvo sa svih kartica na ovoj ploči.", "CardDetailProperty.confirm-property-name-change-subtext": "Stvarno želiš promijeniti svojstvo „{propertyName}” {customText}? To će utjecati na vrijednosti na {numOfCards} kartica na ovoj ploči i može prouzročiti gubitak podataka.", - "CardDetailProperty.confirm-property-type-change": "Potvrdi promjenu vrste svojstva!", + "CardDetailProperty.confirm-property-type-change": "Potvrdi promjenu vrste svojstva", "CardDetailProperty.delete-action-button": "Izbriši", "CardDetailProperty.property-change-action-button": "Promijeni svojstvo", "CardDetailProperty.property-changed": "Promjena svojstva uspjela!", "CardDetailProperty.property-deleted": "Svojstvo {propertyName} uspješno izbrisano!", "CardDetailProperty.property-name-change-subtext": "vrste „{oldPropType}” u „{newPropType}”", - "CardDetailProperty.property-type-change-subtext": "ime u „{newPropName}”", - "CardDialog.copiedLink": "Kopirano!", - "CardDialog.copyLink": "Kopiraj poveznicu", + "CardDetial.limited-link": "Saznaj više o našim tarifma.", "CardDialog.delete-confirmation-dialog-button-text": "Izbriši", "CardDialog.delete-confirmation-dialog-heading": "Potvrdi brisanje karte!", "CardDialog.editing-template": "Uređuješ predložak.", - "CardDialog.nocard": "Ova kartica ne postoji ili nije dostupna.", + "CardDialog.nocard": "Ova kartica ne postoji ili je nedostupna.", "Categories.CreateCategoryDialog.CancelText": "Odustani", "Categories.CreateCategoryDialog.CreateText": "Stvori", "Categories.CreateCategoryDialog.Placeholder": "Zadaj ime kategoriji", "Categories.CreateCategoryDialog.UpdateText": "Aktualiziraj", "CenterPanel.Login": "Prijava", "CenterPanel.Share": "Dijeli", + "CloudMessage.cloud-server": "Nabavi vlastiti besplatni poslužitelj u oblaku.", "ColorOption.selectColor": "Odaberi boju {color}", "Comment.delete": "Izbriši", "CommentsList.send": "Pošalji", @@ -111,13 +122,14 @@ "ContentBlock.moveDown": "Pomakni dolje", "ContentBlock.moveUp": "Pomakni gore", "ContentBlock.text": "tekst", - "DateRange.clear": "Prazno", + "DateRange.clear": "Isprazni", "DateRange.empty": "Prazno", "DateRange.endDate": "Datum kraja", "DateRange.today": "Danas", "DeleteBoardDialog.confirm-cancel": "Odustani", "DeleteBoardDialog.confirm-delete": "Izbriši", - "DeleteBoardDialog.confirm-info": "Stvarno želiš izbrisati ploču „{boardTitle}”? Brisanjem će se izbrisati sve karte na ploči.", + "DeleteBoardDialog.confirm-info": "Stvarno želiš izbrisati ploču „{boardTitle}”? Brisanjem će se izbrisati sve kartice na ploči.", + "DeleteBoardDialog.confirm-info-template": "Stvrano želiš izbrisati predložak ploče „{boardTitle}”?", "DeleteBoardDialog.confirm-tite": "Potvrdi brisanje ploče", "DeleteBoardDialog.confirm-tite-template": "Potvrdi brisanje predloška za ploče", "Dialog.closeDialog": "Zatvori dijalog", @@ -130,23 +142,16 @@ "Filter.not-includes": "ne uključuje", "FilterComponent.add-filter": "+ Dodaj filtar", "FilterComponent.delete": "Izbriši", - "FindBoFindBoardsDialog.IntroText": "Traži ploče", + "FindBoardsDialog.IntroText": "Traži ploče", "FindBoardsDialog.NoResultsFor": "Nema rezultata za „{searchQuery}”", "FindBoardsDialog.NoResultsSubtext": "Provjeri pravopis ili pretraži s jednim drugim pojmom.", "FindBoardsDialog.SubTitle": "Utipkaj ime za pronalaženje ploče. Koristi GORE/DOLJE za pretraživanje. ENTER za odabiranje, ESC za prekid", "FindBoardsDialog.Title": "Pronađi ploče", - "GalleryCard.copiedLink": "Kopirano!", - "GalleryCard.copyLink": "Kopiraj poveznicu", - "GalleryCard.delete": "Izbriši", - "GalleryCard.duplicate": "Dupliciraj", "GroupBy.hideEmptyGroups": "Sakrij {count} prazne grupe", "GroupBy.showHiddenGroups": "Prikaži {count} skrivene grupe", "GroupBy.ungroup": "Razgrupiraj", - "KanbanCard.copiedLink": "Kopirano!", - "KanbanCard.copyLink": "Kopiraj poveznicu", - "KanbanCard.delete": "Izbriši", - "KanbanCard.duplicate": "Dupliciraj", "KanbanCard.untitled": "Bez naslova", + "Mutator.new-board-from-template": "nova ploča iz predloška", "Mutator.new-card-from-template": "nova kartica iz predloška", "Mutator.new-template-from-card": "novi predložak iz kartice", "OnboardingTour.AddComments.Body": "Probleme možeš komentirati. Možeš čak i @spomenuti svoje Mattermost kolege za privlačenje njihove pozornosti.", @@ -157,7 +162,7 @@ "OnboardingTour.AddProperties.Title": "Dodaj svojstva", "OnboardingTour.AddView.Body": "Prijeđi ovamo za stvaranje novog prikaza za organiziranje tvoje ploče koristeći različite rasporede.", "OnboardingTour.AddView.Title": "Dodaj novi prikaz", - "OnboardingTour.CopyLink.Body": "Svoje kartice možeš dijeliti s članovima tima kopiranjem i umetanjem poveznice u kanal, izravnu poruku ili grupnu poruku.", + "OnboardingTour.CopyLink.Body": "Svoje kartice možeš dijeliti s članovima tima pomuću kopiranja i umetanja poveznice u kanal, izravnu poruku ili grupnu poruku.", "OnboardingTour.CopyLink.Title": "Kopiraj poveznicu", "OnboardingTour.OpenACard.Body": "Otvori jednu karticu i istraži načine kako ti Ploče mogu pomoći organizirati tvoj rad.", "OnboardingTour.OpenACard.Title": "Otvori jednu karticu", @@ -189,15 +194,16 @@ "RegistrationLink.description": "Dijeli ovu poveznicu kako bi drugi mogli stvoriti račune:", "RegistrationLink.regenerateToken": "Ponovo generiraj token", "RegistrationLink.tokenRegenerated": "Poveznica za registraciju je ponovno generirana", - "ShareBoard.PublishDescription": "Objavi i dijeli poveznicu „samo za čitanje” sa svima na webu", + "ShareBoard.PublishDescription": "Objavi i dijeli poveznicu „samo za čitanje” sa svima na webu.", "ShareBoard.PublishTitle": "Objavi na webu", "ShareBoard.ShareInternal": "Dijeli interno", - "ShareBoard.ShareInternalDescription": "Korisnici koji imaju dozvole moći će koristiti ovu poveznicu", + "ShareBoard.ShareInternalDescription": "Korisnici koji imaju dozvole moći će koristiti ovu poveznicu.", "ShareBoard.Title": "Dijeli ploču", "ShareBoard.confirmRegenerateToken": "Ovo će poništiti prethodno dijeljene poveznice. Nastaviti?", "ShareBoard.copiedLink": "Kopirano!", "ShareBoard.copyLink": "Kopiraj poveznicu", "ShareBoard.regenerate": "Ponovo generiraj token", + "ShareBoard.searchPlaceholder": "Traži ljude", "ShareBoard.teamPermissionsText": "Svatko u timu {teamName}", "ShareBoard.tokenRegenrated": "Token je ponovo generiran", "ShareBoard.userPermissionsRemoveMemberText": "Ukloni člana", @@ -255,7 +261,8 @@ "View.NewBoardTitle": "Prikaz ploče", "View.NewCalendarTitle": "Prikaz kalendara", "View.NewGalleryTitle": "Prikaz galerije", - "View.NewTableTitle": "Prikaz tablie", + "View.NewTableTitle": "Prikaz tablice", + "View.NewTemplateTitle": "Neimenovani predložak", "View.Table": "Tablica", "ViewHeader.add-template": "Novi predložak", "ViewHeader.delete-template": "Izbriši", @@ -271,7 +278,6 @@ "ViewHeader.new": "Novo", "ViewHeader.properties": "Svojstva", "ViewHeader.properties-menu": "Izbornik svojstava", - "ViewHeader.search": "Pretraga", "ViewHeader.search-text": "Traži kartice", "ViewHeader.select-a-template": "Odaberi predložak", "ViewHeader.set-default-template": "Postavi kao zadano", @@ -279,34 +285,81 @@ "ViewHeader.untitled": "Bez naslova", "ViewHeader.view-header-menu": "Prikaz izbornika zaglavlja", "ViewHeader.view-menu": "Prikaz izbornika", + "ViewLimitDialog.Heading": "Ograničenje prikaza po ploči dosegnuta", + "ViewLimitDialog.PrimaryButton.Title.Admin": "Nadogradi", + "ViewLimitDialog.PrimaryButton.Title.RegularUser": "Obavijesti aministratora", + "ViewLimitDialog.Subtext.Admin": "Nadogradi na našu profesionalnu tarifu ili na tarifu za poduzeća za neograničen broj prikaza po pločama, neograničen broj kartica i još više.", + "ViewLimitDialog.Subtext.Admin.PricingPageLink": "Saznaj više o našim tarifama.", + "ViewLimitDialog.Subtext.RegularUser": "Obavijesti svog administratora da nadogradi na našu profesionalnu tarifu ili na tarifu za poduzeća za neograničen broj prikaza po pločama, neograničen broj kartica i još više.", + "ViewLimitDialog.UpgradeImg.AltText": "nadogradi sliku", + "ViewLimitDialog.notifyAdmin.Success": "Tvoj je administrator obaviješten", "ViewTitle.hide-description": "sakrij opis", "ViewTitle.pick-icon": "Odaberi ikonu", "ViewTitle.random-icon": "Slučajno", "ViewTitle.remove-icon": "Ukloni ikonu", "ViewTitle.show-description": "prikaži opis", - "ViewTitle.untitled-board": "Ploča bez naslova", - "WelcomePage.Description": "„Ploče” je alat za upravljanje projektima koji pomaže definirati, organizirati, pratiti i upravljati radovima timova, koristeći poznati kanban prikaz ploče", + "ViewTitle.untitled-board": "Bezimena ploča", + "WelcomePage.Description": "„Ploče” je alat za upravljanje projektima koji pomaže definirati, organizirati, pratiti i upravljati radovima timova, koristeći poznati Kanban prikaz ploče.", "WelcomePage.Explore.Button": "Uvod u rad programa", "WelcomePage.Heading": "Dobro došao, dobro došla u „Ploče”", "WelcomePage.NoThanks.Text": "Ne hvala", "Workspace.editing-board-template": "Uređuješ predložak ploče.", + "boardSelector.confirm-link-board": "Poveži ploču s kanalom", + "boardSelector.confirm-link-board-button": "Da, poveži ploču", + "boardSelector.confirm-link-board-subtext": "Povezivanje ploče „{boardName}” s ovim kanalom dalo bi svim članovima ovog kanala pristup ploči kao „Urednik”. Stvarmo je želiš povezati?", + "boardSelector.create-a-board": "Stvori ploču", + "boardSelector.link": "Poveži", + "boardSelector.search-for-boards": "Traži ploče", + "boardSelector.title": "Poveži ploče", + "boardSelector.unlink": "Odspoji", "calendar.month": "Mjesec", "calendar.today": "DANAS", "calendar.week": "Tjedan", + "cloudMessage.learn-more": "Saznaj više", "createImageBlock.failed": "Nije moguće prenijeti datoteku. Dosegnuto je ograničenje veličine datoteke.", "default-properties.badges": "Komentari i opis", "default-properties.title": "Naslov", + "error.back-to-home": "Natrag na početnu stranicu", + "error.back-to-team": "Natrag u tim", + "error.board-not-found": "Ploča nije pronađena.", + "error.go-login": "Prijavi se", + "error.invalid-read-only-board": "Nemaš pristup ovoj ploči. Prijavi se za pristup pločama.", + "error.not-logged-in": "Tvoja sesija je možda istekla ili nisi prijavljen/a. Ponovo se prijavi za pristup pločama.", "error.page.title": "Oprosti, dogodila se greška", + "error.team-undefined": "Nije valjani tim.", + "error.unknown": "Dogodila se greška.", "generic.previous": "Prethodno", "imagePaste.upload-failed": "Neke datoteke nisu prenesene. Dosegnuto je ograničenje veličine datoteke", + "limitedCard.title": "Skrivene kartice", "login.log-in-button": "Prijavi se", "login.log-in-title": "Prijavi se", "login.register-button": "ili stvori račun, ako ga još nemaš", + "notification-box-card-limit-reached.close-tooltip": "Postavi pripravno stanje na 10 dana", + "notification-box-card-limit-reached.contact-link": "obavijesti svog administratora", + "notification-box-card-limit-reached.link": "Nadogradi na plaćenu tarifu", + "notification-box-card-limit-reached.title": "Skrivene karte iz ploče: {cards}", + "notification-box-cards-hidden.title": "Ova radnja je sakrlia jednu drugu karticu", + "notification-box.card-limit-reached.not-admin.text": "Za pristupanje arhiviranim karticama, možeš {contactLink} za nadogradnju na plaćenu tarifu.", + "notification-box.card-limit-reached.text": "Dosegnuto je ograničenje broja kartica. Za pregled starijih kartica, {link}", "register.login-button": "ili se prijavi ako već imaš račun", "register.signup-title": "Prijavi se na svoj račun", + "rhs-boards.add": "Dodaj", + "rhs-boards.dm": "DP", + "rhs-boards.gm": "GP", + "rhs-boards.header.dm": "ovu izravnu poruku", + "rhs-boards.header.gm": "ovu grupnu poruku", + "rhs-boards.last-update-at": "Zadnje aktualiziranje: {datetime}", + "rhs-boards.link-boards-to-channel": "Poveži ploče s kanalom {channelName}", + "rhs-boards.linked-boards": "Povezane ploče", + "rhs-boards.no-boards-linked-to-channel": "Do sada nije povezana nijedna ploča s kanalom {channelName}", + "rhs-boards.no-boards-linked-to-channel-description": "Ploče su alat za upravljanje projektima koji pomaže definirati, organizirati, pratiti i upravljati rad timova, koristeći poznati kanban prikaz ploče.", + "rhs-boards.unlink-board": "Odspoji ploču", + "rhs-channel-boards-header.title": "Ploče", "share-board.publish": "Objavi", "share-board.share": "Dijeli", + "shareBoard.channels-select-group": "Kanali", "shareBoard.lastAdmin": "Ploče moraju imati barem jednog administratora", + "shareBoard.members-select-group": "Članovi", "tutorial_tip.finish_tour": "Gotovo", "tutorial_tip.got_it": "Razumijem", "tutorial_tip.ok": "Dalje", diff --git a/webapp/i18n/hu.json b/webapp/i18n/hu.json index f4ae447fd..408c838b7 100644 --- a/webapp/i18n/hu.json +++ b/webapp/i18n/hu.json @@ -8,9 +8,12 @@ "BoardComponent.no-property-title": "Elemek üres {property} tulajdonsággal kerülnek ide. Ez az oszlop nem eltávolítható.", "BoardComponent.show": "Mutat", "BoardMember.schemeAdmin": "Admin", + "BoardMember.schemeCommenter": "Véleményező", "BoardMember.schemeEditor": "Szerkesztő", "BoardMember.schemeNone": "Nincs", "BoardMember.schemeViewer": "Megtekintő", + "BoardMember.schemeViwer": "Megtekintő", + "BoardMember.unlinkChannel": "Leválasztás", "BoardPage.newVersion": "Elérhető a Táblák egy új verziója, kattintson ide az újratöltéshez.", "BoardPage.syncFailed": "A tábla törölve lett vagy hozzáférés vissza lett vonva.", "BoardTemplateSelector.add-template": "Új sablon", @@ -23,6 +26,7 @@ "BoardTemplateSelector.title": "Tábla létrehozása", "BoardTemplateSelector.use-this-template": "Használja ezt a sablont", "BoardsSwitcher.Title": "Táblák keresése", + "BoardsUnfurl.Limited": "A kártya archiválása miatt a további részletek rejtve vannak", "BoardsUnfurl.Remainder": "+{remainder} további", "BoardsUnfurl.Updated": "Frissítve {time}", "Calculations.Options.average.displayName": "Átlag", @@ -30,13 +34,13 @@ "Calculations.Options.count.displayName": "Mennyiség", "Calculations.Options.count.label": "Mennyiség", "Calculations.Options.countChecked.displayName": "Kijelölt", - "Calculations.Options.countChecked.label": "Kijelöltek számossága", + "Calculations.Options.countChecked.label": "Kijelöltek száma", "Calculations.Options.countUnchecked.displayName": "Nem kijelölt", - "Calculations.Options.countUnchecked.label": "Nem kijelöltek számossága", + "Calculations.Options.countUnchecked.label": "Nem kijelöltek száma", "Calculations.Options.countUniqueValue.displayName": "Egyedi", - "Calculations.Options.countUniqueValue.label": "Egyedi értékek számossága", + "Calculations.Options.countUniqueValue.label": "Egyedi értékek száma", "Calculations.Options.countValue.displayName": "Értékek", - "Calculations.Options.countValue.label": "Értékek számossága", + "Calculations.Options.countValue.label": "Értékek száma", "Calculations.Options.dateRange.displayName": "Tartomány", "Calculations.Options.dateRange.label": "Tartomány", "Calculations.Options.earliest.displayName": "Korábbi", @@ -59,6 +63,11 @@ "Calculations.Options.range.label": "Tartomány", "Calculations.Options.sum.displayName": "Összeg", "Calculations.Options.sum.label": "Összeg", + "CalendarCard.untitled": "Névtelen", + "CardActionsMenu.copiedLink": "Másolva!", + "CardActionsMenu.copyLink": "Link másolása", + "CardActionsMenu.delete": "Törlés", + "CardActionsMenu.duplicate": "Duplikálás", "CardBadges.title-checkboxes": "Teendők", "CardBadges.title-comments": "Megjegyzések", "CardBadges.title-description": "Ennek a kártyának van leírása", @@ -68,20 +77,21 @@ "CardDetail.add-icon": "Ikon hozzáadása", "CardDetail.add-property": "+ Tulajdonság hozzáadása", "CardDetail.addCardText": "kártya szövegének hozzáadása", - "CardDetail.moveContent": "kártya tartalmának mozgatása", + "CardDetail.limited-body": "Az archivált kártyák megtekintéséhez, a táblánkénti korlátlan megtekintéshez, korlátlan számú kártyához és még sok máshoz váltson a Professional vagy Enterprise csomagra.", + "CardDetail.limited-button": "Kiadás váltása", + "CardDetail.limited-title": "Ez a kártya rejtett", + "CardDetail.moveContent": "Kártya tartalmának mozgatása", "CardDetail.new-comment-placeholder": "Megjegyzés hozzáadása...", "CardDetailProperty.confirm-delete-heading": "Tulajdonság törlésének jóváhagyása", "CardDetailProperty.confirm-delete-subtext": "Biztos benne, hogy törölni szeretné a \"{propertyName}\" tulajdonságot? A törléssel a tulajdonság minden kártyáról el lesz távolítva.", "CardDetailProperty.confirm-property-name-change-subtext": "Biztosan szeretné megváltoztatni a \"{propertyName}\" tulajdonság {customText}? Ez érint {numOfCards} kártya adatát ebben a táblában, és akár adatvesztéssel is járhat.", - "CardDetailProperty.confirm-property-type-change": "Hagyja jóvá a tulajdonság típusának módosítását!", + "CardDetailProperty.confirm-property-type-change": "Hagyja jóvá a tulajdonság típusának módosítását", "CardDetailProperty.delete-action-button": "Törlés", "CardDetailProperty.property-change-action-button": "Tulajdonság módosítása", "CardDetailProperty.property-changed": "Tulajdonság sikeresen módosult!", "CardDetailProperty.property-deleted": "{propertyName} törlése sikeres!", "CardDetailProperty.property-name-change-subtext": "típus erről: \"{oldPropType}\" erre: \"{newPropType}\"", - "CardDetailProperty.property-type-change-subtext": "név erre: \"{newPropName}\"", - "CardDialog.copiedLink": "Másolva!", - "CardDialog.copyLink": "Link másolása", + "CardDetial.limited-link": "Tudjon meg többet csomagjainkról.", "CardDialog.delete-confirmation-dialog-button-text": "Törlés", "CardDialog.delete-confirmation-dialog-heading": "Hagyja jóvá a kártya törlését!", "CardDialog.editing-template": "Ön egy sablont szerkeszt.", @@ -92,6 +102,7 @@ "Categories.CreateCategoryDialog.UpdateText": "Frissítés", "CenterPanel.Login": "Bejelentkezés", "CenterPanel.Share": "Megosztás", + "CloudMessage.cloud-server": "Szerezze be saját ingyenes felhőszerverét.", "ColorOption.selectColor": "{color} szín kiválasztása", "Comment.delete": "Törlés", "CommentsList.send": "Küldés", @@ -118,6 +129,7 @@ "DeleteBoardDialog.confirm-cancel": "Mégsem", "DeleteBoardDialog.confirm-delete": "Törlés", "DeleteBoardDialog.confirm-info": "Biztos benne, hogy törölni szeretné a “{boardTitle}” táblát? A törlésével az összes benne lévő kártya is törlődni fog.", + "DeleteBoardDialog.confirm-info-template": "Biztos, hogy törölni szeretné a \"{boardTitle}\" tábla sablont?", "DeleteBoardDialog.confirm-tite": "Tábla törlésének jóváhagyása", "DeleteBoardDialog.confirm-tite-template": "Tábla sablon törlésének jóváhagyása", "Dialog.closeDialog": "Ablak bezárása", @@ -130,23 +142,16 @@ "Filter.not-includes": "nem tartalmazza", "FilterComponent.add-filter": "+ Szűrő hozzáadása", "FilterComponent.delete": "Törlés", - "FindBoFindBoardsDialog.IntroText": "Táblák keresése", + "FindBoardsDialog.IntroText": "Táblák keresése", "FindBoardsDialog.NoResultsFor": "Nincs találat a \"{searchQuery}\" kereséshez", "FindBoardsDialog.NoResultsSubtext": "Ellenőrizze az elgépelést vagy próbáljon egy új keresést.", "FindBoardsDialog.SubTitle": "Gépeljen, hogy megtalálja a táblát. Használja a FEL/LE gombokat a böngészéshez. ENTER gombot a kiválasztáshoz és ESC gombot az eldobáshoz", "FindBoardsDialog.Title": "Táblák keresése", - "GalleryCard.copiedLink": "Másolva!", - "GalleryCard.copyLink": "Link másolása", - "GalleryCard.delete": "Törlés", - "GalleryCard.duplicate": "Duplikálás", "GroupBy.hideEmptyGroups": "{count} üres csoport elrejtése", "GroupBy.showHiddenGroups": "{count} rejtett csoport megjelenítése", "GroupBy.ungroup": "Csoportosítás megszüntetése", - "KanbanCard.copiedLink": "Másolt!", - "KanbanCard.copyLink": "Link másolása", - "KanbanCard.delete": "Törlés", - "KanbanCard.duplicate": "Duplikálás", "KanbanCard.untitled": "Névtelen", + "Mutator.new-board-from-template": "új tábla sablon alapján", "Mutator.new-card-from-template": "új kártya sablonból", "Mutator.new-template-from-card": "új sablon kártyából", "OnboardingTour.AddComments.Body": "Hozzászólhat a témákhoz, sőt, más Mattermost felhasználó társát is @megemlítheti, hogy felhívja a figyelmüket.", @@ -189,15 +194,16 @@ "RegistrationLink.description": "Ossza meg ezt a linket másokkat a fiók létrehozásához:", "RegistrationLink.regenerateToken": "Token újragenerálása", "RegistrationLink.tokenRegenerated": "Regisztrációs link újragenerálva", - "ShareBoard.PublishDescription": "\"Csak olvasható\" link közzététele és megosztása mindenkivel a weben", + "ShareBoard.PublishDescription": "Csak olvasható link közzététele és megosztása mindenkivel a weben.", "ShareBoard.PublishTitle": "Közzététel a webre", "ShareBoard.ShareInternal": "Megosztás belsőleg", - "ShareBoard.ShareInternalDescription": "A jogosultságokkal rendelkező felhasználók használhatják ezt a linket", + "ShareBoard.ShareInternalDescription": "A jogosultságokkal rendelkező felhasználók használhatják ezt a linket.", "ShareBoard.Title": "Tábla megosztása", "ShareBoard.confirmRegenerateToken": "Ez érvényteleníteni fogja a korábban megosztott linkeket. Folytassuk?", "ShareBoard.copiedLink": "Másolt!", "ShareBoard.copyLink": "Link másolása", "ShareBoard.regenerate": "Token újragenerálása", + "ShareBoard.searchPlaceholder": "Személyek keresése", "ShareBoard.teamPermissionsText": "Mindenki a {teamName} Csapatban", "ShareBoard.tokenRegenrated": "Token újragenerálva", "ShareBoard.userPermissionsRemoveMemberText": "Tag eltávolítása", @@ -256,6 +262,7 @@ "View.NewCalendarTitle": "Naptár nézet", "View.NewGalleryTitle": "Galéria nézet", "View.NewTableTitle": "Táblázat nézet", + "View.NewTemplateTitle": "Névtelen sablon", "View.Table": "Táblázat", "ViewHeader.add-template": "Új sablon", "ViewHeader.delete-template": "Törlés", @@ -271,7 +278,6 @@ "ViewHeader.new": "Új", "ViewHeader.properties": "Tulajdonságok", "ViewHeader.properties-menu": "Tulajdonságok menü", - "ViewHeader.search": "Keresés", "ViewHeader.search-text": "Kártya keresése", "ViewHeader.select-a-template": "Sablon kiválasztása", "ViewHeader.set-default-template": "Beállítás alapértelmezettnek", @@ -279,34 +285,81 @@ "ViewHeader.untitled": "Névtelen", "ViewHeader.view-header-menu": "Fejléc menü megjelenítése", "ViewHeader.view-menu": "Megtekintés menü", + "ViewLimitDialog.Heading": "Táblánkénti megtekintések korlátja elérve", + "ViewLimitDialog.PrimaryButton.Title.Admin": "Előfizetés váltása", + "ViewLimitDialog.PrimaryButton.Title.RegularUser": "Admin értesítése", + "ViewLimitDialog.Subtext.Admin": "A Professional vagy Enterprise csomagra való váltással korlátlan számú megtekintést kaphat táblánként, korlátlan számú kártyát és még sok mást.", + "ViewLimitDialog.Subtext.Admin.PricingPageLink": "Tudjon meg többet a csomagjainkról.", + "ViewLimitDialog.Subtext.RegularUser": "Értesítse a rendszergazdát, hogy frissíthessen a Professional vagy Enterprise csomagra, hogy korlátlan megtekintést kapjon táblánként, korlátlan számú kártyát és még többet.", + "ViewLimitDialog.UpgradeImg.AltText": "előfizetés váltás kép", + "ViewLimitDialog.notifyAdmin.Success": "A rendszergazdája értesítve lett", "ViewTitle.hide-description": "leírás elrejtése", "ViewTitle.pick-icon": "Válasszon ikont", "ViewTitle.random-icon": "Véletlen", "ViewTitle.remove-icon": "Ikon eltávolítása", "ViewTitle.show-description": "leírás mutatása", "ViewTitle.untitled-board": "Névtelen tábla", - "WelcomePage.Description": "A Táblák egy projekt kezelő segédeszköz ami segít azonosítani, rendezni, követni és vezetni a munkát csapatok között, egy ismerős kanban táblás nézet segítségével", + "WelcomePage.Description": "A Táblák egy projekt kezelő segédeszköz ami segít azonosítani, rendezni, követni és vezetni a munkát csapatok között, egy ismerős kanban táblás nézet segítségével.", "WelcomePage.Explore.Button": "Nézze meg a bemutatót", "WelcomePage.Heading": "Üdvözöljük a Táblákban", "WelcomePage.NoThanks.Text": "Nem, köszönöm, majd kitalálom magam", "Workspace.editing-board-template": "Ön egy sablon táblát szerkeszt.", + "boardSelector.confirm-link-board": "Kösse össze a táblát csatornával", + "boardSelector.confirm-link-board-button": "Igen, kösse össze a táblát", + "boardSelector.confirm-link-board-subtext": "A \"{boardName}\" tábla összekapcsolása ezzel a csatornával a csatorna minden tagjának \"Szerkesztő\" hozzáférést biztosít a táblához. Biztos benne, hogy szeretné összekapcsolni?", + "boardSelector.create-a-board": "Tábla létrehozása", + "boardSelector.link": "Összekapcsolás", + "boardSelector.search-for-boards": "Táblák keresése", + "boardSelector.title": "Táblák összekapcsolása", + "boardSelector.unlink": "Leválasztás", "calendar.month": "Hónap", "calendar.today": "MA", "calendar.week": "Hét", + "cloudMessage.learn-more": "Tudjon meg többet", "createImageBlock.failed": "Nem sikerült feltölteni a fájlt. Fájlméret korlát elérve.", "default-properties.badges": "Megjegyzések és leírás", "default-properties.title": "Cím", + "error.back-to-home": "Vissza a kezdőlapra", + "error.back-to-team": "Vissza a csapatba", + "error.board-not-found": "Tábla nem található.", + "error.go-login": "Bejelentkezés", + "error.invalid-read-only-board": "Önnek nincs hozzáférése ehhez a táblához. Jelentkezz be a Táblák eléréséhez.", + "error.not-logged-in": "Lehet, hogy lejárt a munkamenete, vagy nincs bejelentkezve. Jelentkezzen be újra a Táblákhoz való hozzáféréshez.", "error.page.title": "Sajnálom, valami rosszul sikerült", + "error.team-undefined": "Nem egy érvényes csapat.", + "error.unknown": "Hiba lépett fel.", "generic.previous": "Előző", "imagePaste.upload-failed": "Néhány fájl nem került feltöltésre. Fájlméret korlát elérve", + "limitedCard.title": "Rejtett kártyák", "login.log-in-button": "Bejelentkezés", "login.log-in-title": "Bejelentkezés", "login.register-button": "vagy hozzon létre egy fiókot ha még nincs", + "notification-box-card-limit-reached.close-tooltip": "Altatás 10 napig", + "notification-box-card-limit-reached.contact-link": "értesítheti a rendszergazdát", + "notification-box-card-limit-reached.link": "Váltson fizetős csomagra", + "notification-box-card-limit-reached.title": "{cards} kártya rejtve a táblán", + "notification-box-cards-hidden.title": "Ez a művelet elrejtett egy másik kártyát", + "notification-box.card-limit-reached.not-admin.text": "Az archivált kártyák eléréséhez {contactLink}, hogy váltson fizetős csomagra.", + "notification-box.card-limit-reached.text": "A kártyák limitjét elérte, a régebbi kártyák megtekintéséhez {link}", "register.login-button": "vagy jelentkezzen be ha már van fiókja", "register.signup-title": "Regisztráljon fiókjáért", + "rhs-boards.add": "Hozzáadás", + "rhs-boards.dm": "KÜ", + "rhs-boards.gm": "CSÜ", + "rhs-boards.header.dm": "ez egy Közvetlen Üzenet", + "rhs-boards.header.gm": "ez egy Csoportos Üzenet", + "rhs-boards.last-update-at": "Utolsó frissítés: {datetime}", + "rhs-boards.link-boards-to-channel": "Táblák összekapcsolása a {channelName} csatornával", + "rhs-boards.linked-boards": "Összekapcsolt kártyák", + "rhs-boards.no-boards-linked-to-channel": "Még nincs {channelName} csatornához kapcsolódó tábla", + "rhs-boards.no-boards-linked-to-channel-description": "A Boards egy projekt kezelő eszköz, amely segít meghatározni, szervezni, nyomon követni és kezelni a munkát a csapatokon belül, egy ismerős kanban tábla nézet segítségével.", + "rhs-boards.unlink-board": "Tábla leválasztása", + "rhs-channel-boards-header.title": "Táblák", "share-board.publish": "Közzététel", "share-board.share": "Megosztás", + "shareBoard.channels-select-group": "Csatornák", "shareBoard.lastAdmin": "A tábláknak legalább egy Adminisztárorral kell rendelkezniük", + "shareBoard.members-select-group": "Tagok", "tutorial_tip.finish_tour": "Kész", "tutorial_tip.got_it": "Értettem", "tutorial_tip.ok": "Következő", diff --git a/webapp/i18n/id.json b/webapp/i18n/id.json index 1cf7f6512..d708061e9 100644 --- a/webapp/i18n/id.json +++ b/webapp/i18n/id.json @@ -23,8 +23,6 @@ "CardDetail.addCardText": "tambahkan teks kartu", "CardDetail.moveContent": "pindahkan isi kartu", "CardDetail.new-comment-placeholder": "Tambahkan komentar...", - "CardDialog.copiedLink": "Tersalin!", - "CardDialog.copyLink": "Salin pranala", "CardDialog.editing-template": "Anda sedang menyunting sebuah template.", "CardDialog.nocard": "Kartu ini tidak ada atau tidak dapat diakses.", "ColorOption.selectColor": "Pilih Warna {color}", @@ -53,15 +51,7 @@ "Filter.not-includes": "tidak termasuk", "FilterComponent.add-filter": "+ Tambahkan saringan", "FilterComponent.delete": "Hapus", - "GalleryCard.copiedLink": "Tersalin!", - "GalleryCard.copyLink": "Salin pranala", - "GalleryCard.delete": "Hapus", - "GalleryCard.duplicate": "Duplikasikan", "GroupBy.ungroup": "Pisahkan grup", - "KanbanCard.copiedLink": "Tersalin!", - "KanbanCard.copyLink": "Salin pranala", - "KanbanCard.delete": "Hapus", - "KanbanCard.duplicate": "Duplikasikan", "KanbanCard.untitled": "Tidak berjudul", "Mutator.new-card-from-template": "kartu baru dari template", "Mutator.new-template-from-card": "template baru dari kartu", @@ -140,7 +130,6 @@ "ViewHeader.group-by": "Kelompokkan berdasarkan: {property}", "ViewHeader.new": "Buat", "ViewHeader.properties": "Properti-properti", - "ViewHeader.search": "Cari", "ViewHeader.search-text": "Cari teks", "ViewHeader.select-a-template": "Pilih sebuah template", "ViewHeader.sort": "Pengurutan", diff --git a/webapp/i18n/it.json b/webapp/i18n/it.json index 4b9e78f9e..d8987feb6 100644 --- a/webapp/i18n/it.json +++ b/webapp/i18n/it.json @@ -8,9 +8,12 @@ "BoardComponent.no-property-title": "Gli oggetti senza alcuna proprietà {property} andranno qui. Questo campo non può essere rimosso.", "BoardComponent.show": "Mostra", "BoardMember.schemeAdmin": "Amministratore", - "BoardMember.schemeEditor": "Editor", + "BoardMember.schemeCommenter": "Commentatore", + "BoardMember.schemeEditor": "Editore", "BoardMember.schemeNone": "Niente", "BoardMember.schemeViewer": "Vista", + "BoardMember.schemeViwer": "Visualizzatore", + "BoardMember.unlinkChannel": "Rimuovi collegamento", "BoardPage.newVersion": "Una nuova versione di Board è disponibile, clicca qui per ricaricare.", "BoardPage.syncFailed": "La board potrebbe essere cancellata o l'accesso revocato.", "BoardTemplateSelector.add-template": "Nuovo modello", @@ -23,6 +26,7 @@ "BoardTemplateSelector.title": "Crea una bacheca", "BoardTemplateSelector.use-this-template": "Usa questo modello", "BoardsSwitcher.Title": "Trova bacheche", + "BoardsUnfurl.Limited": "Altri dettagli non sono nascosti in quanto la scheda è archiviata", "BoardsUnfurl.Remainder": "+{remainder} di più", "BoardsUnfurl.Updated": "Aggiornato {time}", "Calculations.Options.average.displayName": "Media", @@ -30,13 +34,13 @@ "Calculations.Options.count.displayName": "Conta", "Calculations.Options.count.label": "Conta", "Calculations.Options.countChecked.displayName": "Controllato", - "Calculations.Options.countChecked.label": "Conteggio selezionato", + "Calculations.Options.countChecked.label": "Conteggio selezionati", "Calculations.Options.countUnchecked.displayName": "Non selezionato", "Calculations.Options.countUnchecked.label": "Conteggio non selezionato", "Calculations.Options.countUniqueValue.displayName": "Unico", "Calculations.Options.countUniqueValue.label": "Conta i valori unici", "Calculations.Options.countValue.displayName": "Valori", - "Calculations.Options.countValue.label": "Conta Valore", + "Calculations.Options.countValue.label": "Conteggio valori", "Calculations.Options.dateRange.displayName": "Intervallo", "Calculations.Options.dateRange.label": "Intervallo", "Calculations.Options.earliest.displayName": "Primo", @@ -59,6 +63,11 @@ "Calculations.Options.range.label": "Intervallo", "Calculations.Options.sum.displayName": "Somma", "Calculations.Options.sum.label": "Somma", + "CalendarCard.untitled": "Senza titolo", + "CardActionsMenu.copiedLink": "Copiato!", + "CardActionsMenu.copyLink": "Copia collegamento", + "CardActionsMenu.delete": "Elimina", + "CardActionsMenu.duplicate": "Duplica", "CardBadges.title-checkboxes": "Checkboxes", "CardBadges.title-comments": "Commenti", "CardBadges.title-description": "Questa scheda ha una descrizione", @@ -68,7 +77,9 @@ "CardDetail.add-icon": "Aggiungi icona", "CardDetail.add-property": "+ Aggiungi una proprietà", "CardDetail.addCardText": "aggiungi testo alla scheda", - "CardDetail.moveContent": "sposta il contenuto della scheda", + "CardDetail.limited-button": "Aggiorna", + "CardDetail.limited-title": "Questa scheda è nascosta", + "CardDetail.moveContent": "Sposta il contenuto della scheda", "CardDetail.new-comment-placeholder": "Aggiungi un commento...", "CardDetailProperty.confirm-delete-heading": "Conferma l'eliminazione della proprietà", "CardDetailProperty.confirm-delete-subtext": "Sei sicuro di voler eliminare la proprietà \"{propertyName}\"? Rimuovendola, verranno eliminate le proprietà da tutte le carte in questa board.", @@ -79,9 +90,6 @@ "CardDetailProperty.property-changed": "Cambiata proprietà con successo!", "CardDetailProperty.property-deleted": "Rimozione di {propertyName} effettuata con successo!", "CardDetailProperty.property-name-change-subtext": "tipo da \"{oldPropType}\" a \"{newPropType}\"", - "CardDetailProperty.property-type-change-subtext": "rinomina a \"{newPropName}\"", - "CardDialog.copiedLink": "Copiato!", - "CardDialog.copyLink": "Copia link", "CardDialog.delete-confirmation-dialog-button-text": "Elimina", "CardDialog.delete-confirmation-dialog-heading": "Conferma l'eliminazione della scheda!", "CardDialog.editing-template": "Stai modificando un template.", @@ -126,22 +134,13 @@ "Filter.not-includes": "non include", "FilterComponent.add-filter": "+ Aggiungi un filtro", "FilterComponent.delete": "Elimina", - "FindBoFindBoardsDialog.IntroText": "Cerca bacheche", "FindBoardsDialog.NoResultsFor": "Nessun risultato per \"{searchQuery}\"", "FindBoardsDialog.NoResultsSubtext": "Controlla l'ortografia o prova con un'altra ricerca.", "FindBoardsDialog.SubTitle": "Scrivi per trovare una bacheca. Utilizza i tasti SU/GIÙ per navigare. INVIO per selezionare, ESC per annullare", "FindBoardsDialog.Title": "Trova bacheche", - "GalleryCard.copiedLink": "Copiato!", - "GalleryCard.copyLink": "Copia link", - "GalleryCard.delete": "Elimina", - "GalleryCard.duplicate": "Duplica", "GroupBy.hideEmptyGroups": "Nascondi {count} gruppi vuoti", "GroupBy.showHiddenGroups": "Mostra {count} gruppi nascosti", "GroupBy.ungroup": "Dividi", - "KanbanCard.copiedLink": "Copiato!", - "KanbanCard.copyLink": "Copia link", - "KanbanCard.delete": "Elimina", - "KanbanCard.duplicate": "Duplica", "KanbanCard.untitled": "Senza titolo", "Mutator.new-card-from-template": "nuova scheda da modello", "Mutator.new-template-from-card": "nuovo modello da scheda", @@ -259,7 +258,6 @@ "ViewHeader.new": "Nuovo", "ViewHeader.properties": "Proprietà", "ViewHeader.properties-menu": "Menù delle proprietà", - "ViewHeader.search": "Cerca", "ViewHeader.search-text": "Cerca testo", "ViewHeader.select-a-template": "Seleziona un modello", "ViewHeader.set-default-template": "Imposta come predefinito", diff --git a/webapp/i18n/ja.json b/webapp/i18n/ja.json index f5311f955..0df4e8ac6 100644 --- a/webapp/i18n/ja.json +++ b/webapp/i18n/ja.json @@ -8,9 +8,12 @@ "BoardComponent.no-property-title": "{property}が空のアイテムがここに表示されます。このカラムは削除できません。", "BoardComponent.show": "表示", "BoardMember.schemeAdmin": "管理者", + "BoardMember.schemeCommenter": "コメントした人", "BoardMember.schemeEditor": "編集者", "BoardMember.schemeNone": "なし", "BoardMember.schemeViewer": "閲覧者", + "BoardMember.schemeViwer": "閲覧者", + "BoardMember.unlinkChannel": "リンク解除", "BoardPage.newVersion": "ボードの新しいバージョンが利用可能です。ここをクリックして再読み込みしてください。", "BoardPage.syncFailed": "ボードが削除されたか、アクセスが取り消されました。", "BoardTemplateSelector.add-template": "新しいテンプレート", @@ -23,6 +26,7 @@ "BoardTemplateSelector.title": "ボードを作成する", "BoardTemplateSelector.use-this-template": "このテンプレートを使う", "BoardsSwitcher.Title": "ボードを探す", + "BoardsUnfurl.Limited": "カードがアーカイブされているため詳細は表示されません", "BoardsUnfurl.Remainder": "残り +{remainder}", "BoardsUnfurl.Updated": "更新日時 {time}", "Calculations.Options.average.displayName": "平均", @@ -59,6 +63,11 @@ "Calculations.Options.range.label": "範囲", "Calculations.Options.sum.displayName": "合計", "Calculations.Options.sum.label": "合計", + "CalendarCard.untitled": "無題", + "CardActionsMenu.copiedLink": "コピーしました!", + "CardActionsMenu.copyLink": "リンクをコピー", + "CardActionsMenu.delete": "削除", + "CardActionsMenu.duplicate": "複製", "CardBadges.title-checkboxes": "チェックボックス", "CardBadges.title-comments": "コメント", "CardBadges.title-description": "このカードには説明があります", @@ -68,20 +77,21 @@ "CardDetail.add-icon": "アイコンを追加する", "CardDetail.add-property": "+ プロパティを追加", "CardDetail.addCardText": "カードテキストを追加する", + "CardDetail.limited-body": "無制限のビュー数、無制限のカード数などの機能を利用するために、ProfessionalプランまたはEnterpriseプランにアップグレードしてください。", + "CardDetail.limited-button": "アップグレード", + "CardDetail.limited-title": "このカードは表示できません", "CardDetail.moveContent": "カード内容の移動", "CardDetail.new-comment-placeholder": "コメントを追加する...", "CardDetailProperty.confirm-delete-heading": "プロパティの削除を確定する", "CardDetailProperty.confirm-delete-subtext": "本当にプロパティ \"{propertyName}\" を削除しますか? 削除すると、このボードのすべてのカードからそのプロパティが削除されます。", "CardDetailProperty.confirm-property-name-change-subtext": "本当にプロパティ \"{propertyName}\" の \"{customText}\" に変更しますか? これは、このボードの{numOfCards}カード全体の値に影響し、データの損失につながる恐れがあります。", - "CardDetailProperty.confirm-property-type-change": "プロパティ種別の変更について確認しました!", + "CardDetailProperty.confirm-property-type-change": "プロパティ種別の変更を確定する", "CardDetailProperty.delete-action-button": "削除", "CardDetailProperty.property-change-action-button": "プロパティの変更", "CardDetailProperty.property-changed": "プロパティが変更されました!", "CardDetailProperty.property-deleted": "{propertyName} が正常に削除されました!", "CardDetailProperty.property-name-change-subtext": "種別を \"{oldPropType}\" から\"{newPropType}\" に", - "CardDetailProperty.property-type-change-subtext": "名前を \"{newPropName}\" に", - "CardDialog.copiedLink": "コピーしました!", - "CardDialog.copyLink": "リンクをコピー", + "CardDetial.limited-link": "各プランの詳細についてはこちらをご覧ください。", "CardDialog.delete-confirmation-dialog-button-text": "削除", "CardDialog.delete-confirmation-dialog-heading": "カード削除の確認", "CardDialog.editing-template": "テンプレートを編集しています。", @@ -92,6 +102,7 @@ "Categories.CreateCategoryDialog.UpdateText": "更新", "CenterPanel.Login": "ログイン", "CenterPanel.Share": "共有", + "CloudMessage.cloud-server": "専用の無料クラウドサーバーを入手する。", "ColorOption.selectColor": "{color} 色を選択", "Comment.delete": "削除", "CommentsList.send": "送信", @@ -118,8 +129,9 @@ "DeleteBoardDialog.confirm-cancel": "キャンセル", "DeleteBoardDialog.confirm-delete": "削除", "DeleteBoardDialog.confirm-info": "本当にボード \"{boardTitle}\" を削除しますか? 削除すると、このボードのすべてのカードが削除されます。", - "DeleteBoardDialog.confirm-tite": "ボード削除の確認", - "DeleteBoardDialog.confirm-tite-template": "ボードテンプレートの削除確認", + "DeleteBoardDialog.confirm-info-template": "ボードテンプレート \"{boardTitle}\" を本当に削除しますか?", + "DeleteBoardDialog.confirm-tite": "ボードの削除を確定する", + "DeleteBoardDialog.confirm-tite-template": "ボードテンプレートの削除を確定する", "Dialog.closeDialog": "ダイアログを閉じる", "EditableDayPicker.today": "今日", "Error.mobileweb": "モバイルウェブのサポートは現在、初期ベータ版です。一部の機能が利用できない場合があります。", @@ -130,23 +142,16 @@ "Filter.not-includes": "を含まない", "FilterComponent.add-filter": "+ フィルターを追加する", "FilterComponent.delete": "削除", - "FindBoFindBoardsDialog.IntroText": "ボードを検索する", + "FindBoardsDialog.IntroText": "ボードを検索", "FindBoardsDialog.NoResultsFor": "\"{searchQuery}\"に対する結果はありません", "FindBoardsDialog.NoResultsSubtext": "スペルを確認し、再度検索してください。", "FindBoardsDialog.SubTitle": "ボードを検索するために文字を入力してください。UP/DOWNで閲覧、ENTERで選択、ESCでキャンセル", "FindBoardsDialog.Title": "ボードを探す", - "GalleryCard.copiedLink": "コピーしました!", - "GalleryCard.copyLink": "リンクをコピー", - "GalleryCard.delete": "削除", - "GalleryCard.duplicate": "複製", "GroupBy.hideEmptyGroups": "{count} 個の空のグループを隠す", "GroupBy.showHiddenGroups": "{count} 個の非表示グループを表示する", "GroupBy.ungroup": "グループ解除", - "KanbanCard.copiedLink": "コピーしました!", - "KanbanCard.copyLink": "リンクをコピー", - "KanbanCard.delete": "削除", - "KanbanCard.duplicate": "複製", "KanbanCard.untitled": "無題", + "Mutator.new-board-from-template": "テンプレートからの新しいボード", "Mutator.new-card-from-template": "テンプレートから新しいカードを作成", "Mutator.new-template-from-card": "カードから新しいテンプレートを作成", "OnboardingTour.AddComments.Body": "問題にコメントしたり、仲間のMattermostユーザーの注意を引くために@メンションすることもできます。", @@ -189,15 +194,16 @@ "RegistrationLink.description": "アカウントを作成には、このリンクを共有してください:", "RegistrationLink.regenerateToken": "トークンを再生成する", "RegistrationLink.tokenRegenerated": "登録リンクが再生成されました", - "ShareBoard.PublishDescription": "Web上の全員へ \"読み取り専用\" のリンクを公開および共有する", + "ShareBoard.PublishDescription": "Web上の全員へ \"読み取り専用\" のリンクを公開および共有する。", "ShareBoard.PublishTitle": "Web上へ公開する", "ShareBoard.ShareInternal": "内部で共有する", - "ShareBoard.ShareInternalDescription": "権限のあるユーザーは、このリンクを使用することができます", + "ShareBoard.ShareInternalDescription": "権限のあるユーザーは、このリンクを使用することができます。", "ShareBoard.Title": "ボードを共有する", "ShareBoard.confirmRegenerateToken": "実行すると以前に共有されたリンクは無効になります。続行しますか?", "ShareBoard.copiedLink": "コピーしました!", "ShareBoard.copyLink": "リンクをコピーする", "ShareBoard.regenerate": "トークンを再生成する", + "ShareBoard.searchPlaceholder": "人を検索", "ShareBoard.teamPermissionsText": "{teamName}チームの全員", "ShareBoard.tokenRegenrated": "トークンが再生成されました", "ShareBoard.userPermissionsRemoveMemberText": "メンバーを削除する", @@ -256,6 +262,7 @@ "View.NewCalendarTitle": "カレンダー表示", "View.NewGalleryTitle": "ギャラリービュー", "View.NewTableTitle": "テーブル表示", + "View.NewTemplateTitle": "無題のテンプレート", "View.Table": "テーブル", "ViewHeader.add-template": "新しいテンプレート", "ViewHeader.delete-template": "削除", @@ -271,7 +278,6 @@ "ViewHeader.new": "新規", "ViewHeader.properties": "プロパティ", "ViewHeader.properties-menu": "プロパティメニュー", - "ViewHeader.search": "検索", "ViewHeader.search-text": "カード検索", "ViewHeader.select-a-template": "テンプレート選択", "ViewHeader.set-default-template": "デフォルトとして設定", @@ -279,34 +285,84 @@ "ViewHeader.untitled": "無題", "ViewHeader.view-header-menu": "ヘッダーメニューを見る", "ViewHeader.view-menu": "メニューを見る", + "ViewLimitDialog.Heading": "ボードごとのビュー数制限に達しました", + "ViewLimitDialog.PrimaryButton.Title.Admin": "アップグレード", + "ViewLimitDialog.PrimaryButton.Title.RegularUser": "管理者に連絡する", + "ViewLimitDialog.Subtext.Admin": "無制限のビュー数、無制限のカード数などの機能を利用するために、ProfessionalプランまたはEnterpriseプランにアップグレードしてください。", + "ViewLimitDialog.Subtext.Admin.PricingPageLink": "各プランの詳細についてはこちらをご覧ください。", + "ViewLimitDialog.Subtext.RegularUser": "無制限のビュー数や無制限のカード数などの機能を利用するために、ProfessionalプランまたはEnterpriseプランへアップグレードするよう管理者に連絡してください。", + "ViewLimitDialog.UpgradeImg.AltText": "アップグレードイメージ", + "ViewLimitDialog.notifyAdmin.Success": "管理者に通知されました", "ViewTitle.hide-description": "説明を非表示", "ViewTitle.pick-icon": "アイコンを選ぶ", "ViewTitle.random-icon": "ランダム", "ViewTitle.remove-icon": "アイコンを削除する", "ViewTitle.show-description": "説明を表示", "ViewTitle.untitled-board": "無題のボード", - "WelcomePage.Description": "Boardsは、よく知られたKanban形式のビューを使用して、チーム全体の作業を定義、整理、追跡、管理するためのプロジェクト管理ツールです", + "WelcomePage.Description": "Boardsは、よく知られたKanban形式のビューを使用して、チーム全体の作業を定義、整理、追跡、管理するためのプロジェクト管理ツールです。", "WelcomePage.Explore.Button": "ツアーに参加する", "WelcomePage.Heading": "ボードへようこそ", "WelcomePage.NoThanks.Text": "いいえ、自分で調べます", "Workspace.editing-board-template": "ボードのテンプレートを編集しています。", + "boardSelector.confirm-link-board": "ボードをチャンネルへリンク", + "boardSelector.confirm-link-board-button": "はい、ボードをリンクします", + "boardSelector.confirm-link-board-subtext": "\"{boardName}\" ボードをこのチャンネルにリンクすると、このチャンネルのメンバー全員にボードへの \"編集者\" アクセスを与えます。本当にリンクしますか?", + "boardSelector.create-a-board": "ボードを作成", + "boardSelector.link": "リンク", + "boardSelector.search-for-boards": "ボードを検索", + "boardSelector.title": "ボードをリンク", + "boardSelector.unlink": "リンク解除", "calendar.month": "月", "calendar.today": "今日", "calendar.week": "週", + "cloudMessage.learn-more": "さらに詳しく", "createImageBlock.failed": "ファイルをアップロードできません。ファイルサイズの制限に達しています。", "default-properties.badges": "コメントと説明", "default-properties.title": "タイトル", + "error.back-to-home": "ホームへ戻る", + "error.back-to-team": "チームに戻る", + "error.board-not-found": "ボードが見つかりませんでした。", + "error.go-login": "ログイン", + "error.invalid-read-only-board": "このボードにアクセスできません。アクセスするにはログインしてください。", + "error.not-logged-in": "セッションの有効期限が切れているか、ログインしていない可能性があります。ボードにアクセスするには再度ログインしてください。", "error.page.title": "申し訳ありませんが、何か問題が発生しました", + "error.team-undefined": "有効なチームではありません。", + "error.unknown": "エラーが発生しました。", "generic.previous": "前へ", "imagePaste.upload-failed": "一部のファイルをアップロードできませんでした。ファイルサイズの制限に達しています", + "limitedCard.title": "非表示カード", "login.log-in-button": "ログイン", "login.log-in-title": "ログイン", "login.register-button": "アカウントをお持ちでない方はアカウントを作成してください", + "notification-box-card-limit-reached.close-tooltip": "10日間のスヌーズ", + "notification-box-card-limit-reached.contact-link": "管理者に通知する", + "notification-box-card-limit-reached.link": "有料プランへのアップグレード", + "notification-box-card-limit-reached.title": "ボードから {cards} カードが非表示になっています", + "notification-box-cards-hidden.title": "このアクションにより他のカードが非表示になります", + "notification-box.card-limit-reached.not-admin.text": "アーカイブされたカードにアクセスするには、{contactLink}から有料プランにアップグレードしてください。", + "notification-box.card-limit-reached.text": "カード数の制限に達しました。古いカードを閲覧するには、{link}", "register.login-button": "または、すでにアカウントをお持ちの方はログインしてください", "register.signup-title": "アカウント登録", + "rhs-boards.add": "追加", + "rhs-boards.dm": "DM", + "rhs-boards.gm": "GM", + "rhs-boards.header.dm": "このダイレクトメッセージ", + "rhs-boards.header.gm": "このグループメッセージ", + "rhs-boards.last-update-at": "最終更新: {datetime}", + "rhs-boards.link-boards-to-channel": "ボードを{channelName}へリンクする", + "rhs-boards.linked-boards": "リンク済みボード", + "rhs-boards.no-boards-linked-to-channel": "{channelName}にリンクされたボードはまだありません", + "rhs-boards.no-boards-linked-to-channel-description": "Boardsは、よく知られたKanban形式のビューを使用して、チーム全体の作業を定義、生理、追跡、管理するためのプロジェクト管理ツールです。", + "rhs-boards.unlink-board": "ボードのリンクを解除", + "rhs-channel-boards-header.title": "ボード", "share-board.publish": "公開", "share-board.share": "共有", + "shareBoard.channels-select-group": "Channels", + "shareBoard.confirm-link-public-channel": "公開チャンネルを追加しています", + "shareBoard.confirm-link-public-channel-button": "はい、公開チャンネルを追加します", + "shareBoard.confirm-link-public-channel-subtext": "公開チャンネルに参加したメンバー全員がボードへの \"編集者\" アクセスを持つことになりますが、本当に実行しますか?", "shareBoard.lastAdmin": "ボードには少なくとも1名の管理者が必要です", + "shareBoard.members-select-group": "メンバー", "tutorial_tip.finish_tour": "完了", "tutorial_tip.got_it": "了解", "tutorial_tip.ok": "次へ", diff --git a/webapp/i18n/kk.json b/webapp/i18n/kk.json index 78affed9f..bfc369233 100644 --- a/webapp/i18n/kk.json +++ b/webapp/i18n/kk.json @@ -62,9 +62,6 @@ "CardDetailProperty.property-changed": "Property сәтті өзгертілді!", "CardDetailProperty.property-deleted": "{propertyName} Сәтті Жойылды!", "CardDetailProperty.property-name-change-subtext": "\"{oldPropType}\" тен \"{newPropType}\"'қа дейін теріңіз", - "CardDetailProperty.property-type-change-subtext": "\"{newPropName}\" атау берініз", - "CardDialog.copiedLink": "Көшірілді!", - "CardDialog.copyLink": "Сілтемені көшіру", "CardDialog.editing-template": "Сіз үлгіні өзгертудесіз.", "CardDialog.nocard": "Бұл кәрте жоқ немесе қолжетімсіз.", "ColorOption.selectColor": "{color} Түсті Танданыз", @@ -100,15 +97,7 @@ "Filter.not-includes": "кірістірмейді", "FilterComponent.add-filter": "+ Филтір қосу", "FilterComponent.delete": "Жою", - "GalleryCard.copiedLink": "Көшірілді!", - "GalleryCard.copyLink": "Сілтемені көшіру", - "GalleryCard.delete": "Жою", - "GalleryCard.duplicate": "Көшірмесін жасау", "GroupBy.ungroup": "Гірупсіздендіру", - "KanbanCard.copiedLink": "Көшірілді!", - "KanbanCard.copyLink": "Сілтемені көшіру", - "KanbanCard.delete": "Жою", - "KanbanCard.duplicate": "Көшірмесін жасау", "KanbanCard.untitled": "Атаусыз", "Mutator.new-card-from-template": "үлгіден жаңа кәрте жасау", "Mutator.new-template-from-card": "кәртеден жаңа үлгі", @@ -193,7 +182,6 @@ "ViewHeader.group-by": "{property} бойынша гіруптеу", "ViewHeader.new": "Жаңа", "ViewHeader.properties": "Property'лер", - "ViewHeader.search": "Іздеу", "ViewHeader.search-text": "Мәтінді іздеу", "ViewHeader.select-a-template": "Үлгіні таңдау", "ViewHeader.set-default-template": "Әдепкі ретінде орнату", diff --git a/webapp/i18n/ko.json b/webapp/i18n/ko.json index f66d5bf28..767c25985 100644 --- a/webapp/i18n/ko.json +++ b/webapp/i18n/ko.json @@ -75,9 +75,6 @@ "CardDetailProperty.property-changed": "성공적으로 속성이 변경되었습니다!", "CardDetailProperty.property-deleted": "성공적으로 {propertyName}이(가) 삭제되었습니다!", "CardDetailProperty.property-name-change-subtext": "유형을 \"{oldPropType}\"에서 \"{newPropType}\"로", - "CardDetailProperty.property-type-change-subtext": "\"{newPropName}\"로 이름 지정하기", - "CardDialog.copiedLink": "복사되었습니다!", - "CardDialog.copyLink": "링크 복사하기", "CardDialog.delete-confirmation-dialog-button-text": "삭제", "CardDialog.delete-confirmation-dialog-heading": "카드 삭제 확인!", "CardDialog.editing-template": "템플릿을 수정하는 중입니다.", @@ -118,15 +115,7 @@ "Filter.not-includes": "포함하지 않음", "FilterComponent.add-filter": "+ 필터 추가", "FilterComponent.delete": "삭제", - "GalleryCard.copiedLink": "복사되었습니다!", - "GalleryCard.copyLink": "링크 복사하기", - "GalleryCard.delete": "삭제", - "GalleryCard.duplicate": "복제", "GroupBy.ungroup": "그룹 해제", - "KanbanCard.copiedLink": "복사되었습니다!", - "KanbanCard.copyLink": "링크 복사하기", - "KanbanCard.delete": "삭제", - "KanbanCard.duplicate": "복제", "KanbanCard.untitled": "제목 없음", "Mutator.new-card-from-template": "템플릿에서 새 카드 만들기", "Mutator.new-template-from-card": "카드에서 새 템플릿 만들기", @@ -226,7 +215,6 @@ "ViewHeader.new": "생성", "ViewHeader.properties": "속성", "ViewHeader.properties-menu": "속성 메뉴", - "ViewHeader.search": "검색", "ViewHeader.search-text": "검색 문자열", "ViewHeader.select-a-template": "템플릿 선택", "ViewHeader.set-default-template": "기본으로 설정", diff --git a/webapp/i18n/ml.json b/webapp/i18n/ml.json index 860b71095..5cda0fcf7 100644 --- a/webapp/i18n/ml.json +++ b/webapp/i18n/ml.json @@ -8,8 +8,10 @@ "BoardComponent.no-property-title": "ശൂന്യമായ {property} പ്രോപ്പർട്ടി ഉള്ള ഇനങ്ങൾ ഇവിടെ പോകും. ഈ കോളം നീക്കം ചെയ്യാൻ കഴിയില്ല.", "BoardComponent.show": "കാണിക്കുക", "BoardMember.schemeAdmin": "അഡ്മിൻ", + "BoardMember.schemeCommenter": "കമന്റേറ്റർ", "BoardMember.schemeEditor": "എഡിറ്റർ", "BoardMember.schemeNone": "ഒന്നുമില്ല", + "BoardMember.schemeViewer": "കാഴ്ചക്കാരൻ", "BoardPage.newVersion": "ബോർഡുകളുടെ ഒരു പുതിയ പതിപ്പ് ലഭ്യമാണ്, റീലോഡ് ചെയ്യാൻ ഇവിടെ ക്ലിക്ക് ചെയ്യുക.", "BoardPage.syncFailed": "ബോർഡ് ഇല്ലാതാക്കുകയോ ആക്സസ് റദ്ദാക്കുകയോ ചെയ്യാം.", "BoardTemplateSelector.add-template": "പുതിയ ടെംപ്ലേറ്റ്", @@ -78,9 +80,6 @@ "CardDetailProperty.property-changed": "പ്രോപ്പർട്ടി വിജയകരമായി മാറ്റി!", "CardDetailProperty.property-deleted": "വിജയകരമായി {propertyName} ഇല്ലാതാക്കിയിരിക്കുന്നു!", "CardDetailProperty.property-name-change-subtext": "\"{oldPropType}\" മുതൽ \"{newPropType}\" വരെ ടൈപ്പ് ചെയ്യുക", - "CardDetailProperty.property-type-change-subtext": "\"{newPropName}\" എന്നതിലേക്ക് പേര് ചേർക്കുക", - "CardDialog.copiedLink": "പകർത്തി!", - "CardDialog.copyLink": "ലിങ്ക് പകർത്തുക", "CardDialog.delete-confirmation-dialog-button-text": "ഇല്ലാതാക്കുക", "CardDialog.delete-confirmation-dialog-heading": "കാർഡ് ഇല്ലാതാക്കൽ സ്ഥിരീകരിക്കുക!", "CardDialog.editing-template": "നിങ്ങൾ ഒരു ടെംപ്ലേറ്റ് എഡിറ്റ് ചെയ്യുകയാണ്.", @@ -127,22 +126,13 @@ "Filter.not-includes": "ഉൾപ്പെടുന്നില്ല", "FilterComponent.add-filter": "+ ഫിൽട്ടർ ചേർക്കുക", "FilterComponent.delete": "ഇല്ലാതാക്കുക", - "FindBoFindBoardsDialog.IntroText": "ബോർഡുകൾക്കായി തിരയുക", "FindBoardsDialog.NoResultsFor": "\"{searchQuery}\" എന്നതിന് ഫലങ്ങളൊന്നുമില്ല", "FindBoardsDialog.NoResultsSubtext": "അക്ഷരവിന്യാസം പരിശോധിക്കുക അല്ലെങ്കിൽ മറ്റൊരു തിരയൽ പരീക്ഷിക്കുക.", "FindBoardsDialog.SubTitle": "ഒരു ബോർഡ് കണ്ടെത്താൻ ടൈപ്പ് ചെയ്യുക. ബ്രൗസ് ചെയ്യാൻ UP/DOWN ഉപയോഗിക്കുക. തിരഞ്ഞെടുക്കാൻ ENTER, ഡിസ്മിസ് ചെയ്യാൻ ESC", "FindBoardsDialog.Title": "ബോർഡുകൾ കണ്ടെത്തുക", - "GalleryCard.copiedLink": "പകർത്തി!", - "GalleryCard.copyLink": "ലിങ്ക് പകർത്തുക", - "GalleryCard.delete": "ഇല്ലാതാക്കുക", - "GalleryCard.duplicate": "തനിപ്പകർപ്പ്", "GroupBy.hideEmptyGroups": "{count} ശൂന്യമായ ഗ്രൂപ്പുകൾ മറയ്ക്കുക", "GroupBy.showHiddenGroups": "മറഞ്ഞിരിക്കുന്ന {count} ഗ്രൂപ്പുകൾ കാണിക്കുക", "GroupBy.ungroup": "ഗ്രൂപ്പിൽ നിന്നും മാറ്റുക", - "KanbanCard.copiedLink": "പകർത്തി!", - "KanbanCard.copyLink": "ലിങ്ക് പകർത്തുക", - "KanbanCard.delete": "ഇല്ലാതാക്കുക", - "KanbanCard.duplicate": "തനിപ്പകർപ്പ്", "KanbanCard.untitled": "ശീർഷകമില്ലാത്തത്", "Mutator.new-card-from-template": "ടെംപ്ലേറ്റിൽ നിന്നുള്ള പുതിയ കാർഡ്", "Mutator.new-template-from-card": "കാർഡിൽ നിന്നുള്ള പുതിയ ടെംപ്ലേറ്റ്", @@ -264,7 +254,6 @@ "ViewHeader.new": "പുതിയത്", "ViewHeader.properties": "സവിശേഷതകള്‍", "ViewHeader.properties-menu": "പ്രോപ്പർട്ടീസ് മെനു", - "ViewHeader.search": "തിരയൽ", "ViewHeader.search-text": "കാർഡുകൾ തിരയുക", "ViewHeader.select-a-template": "ഒരു ടെംപ്ലേറ്റ് തിരഞ്ഞെടുക്കുക", "ViewHeader.set-default-template": "സ്ഥിരസ്ഥിതിയായി സജ്ജമാക്കാൻ", diff --git a/webapp/i18n/nl.json b/webapp/i18n/nl.json index 817d68913..031316bf3 100644 --- a/webapp/i18n/nl.json +++ b/webapp/i18n/nl.json @@ -8,21 +8,25 @@ "BoardComponent.no-property-title": "Items met een lege {property} eigenschap komen hier te staan. Deze kolom kan niet worden verwijderd.", "BoardComponent.show": "Toon", "BoardMember.schemeAdmin": "Beheerder", + "BoardMember.schemeCommenter": "Commentator", "BoardMember.schemeEditor": "Bewerker", "BoardMember.schemeNone": "Geen", "BoardMember.schemeViewer": "Toeschouwer", + "BoardMember.schemeViwer": "Kijker", + "BoardMember.unlinkChannel": "Losmaken", "BoardPage.newVersion": "Er is een nieuwe versie van Boards, klik hier om te herladen.", "BoardPage.syncFailed": "Het bord kan worden verwijderd of de toegang kan worden ingetrokken.", "BoardTemplateSelector.add-template": "Nieuw sjabloon", "BoardTemplateSelector.create-empty-board": "Maak een leeg bord", "BoardTemplateSelector.delete-template": "Verwijderen", - "BoardTemplateSelector.description": "Kies een sjabloon om je op weg te helpen. Pas het sjabloon eenvoudig aan jouw behoeften aan, of maak een leeg bord om vanaf nul te beginnen.", + "BoardTemplateSelector.description": "Voeg een bord aan de zijbalk door één van onderstaande sjabloon te gebruiken of start helemaal vanaf nul.", "BoardTemplateSelector.edit-template": "Bewerken", - "BoardTemplateSelector.plugin.no-content-description": "Voeg een bord toe aan de zijbalk door gebruik te maken van één van de sjablonen die hieronder zijn gedefinieerd of begin vanaf nul.{lineBreak} Leden van \"{teamName}\" zullen toegang hebben tot de boards die hier zijn gemaakt.", - "BoardTemplateSelector.plugin.no-content-title": "Maak een Board in {teamName}", - "BoardTemplateSelector.title": "Maak een Board", + "BoardTemplateSelector.plugin.no-content-description": "Voeg een bord aan de zijbalk door één van onderstaande sjabloon te gebruiken of start helemaal vanaf nul.", + "BoardTemplateSelector.plugin.no-content-title": "Een bord aanmaken", + "BoardTemplateSelector.title": "Maak een board", "BoardTemplateSelector.use-this-template": "Gebruik dit sjabloon", "BoardsSwitcher.Title": "Boards vinden", + "BoardsUnfurl.Limited": "Extra details zijn verborgen omdat de kaart gearchiveerd is", "BoardsUnfurl.Remainder": "+{remainder} meer", "BoardsUnfurl.Updated": "Bijgewerkt {time}", "Calculations.Options.average.displayName": "Gemiddeld", @@ -59,6 +63,11 @@ "Calculations.Options.range.label": "Bereik", "Calculations.Options.sum.displayName": "Som", "Calculations.Options.sum.label": "Som", + "CalendarCard.untitled": "Titelloos", + "CardActionsMenu.copiedLink": "Gekopieerd!", + "CardActionsMenu.copyLink": "Kopieer link", + "CardActionsMenu.delete": "Verwijderen", + "CardActionsMenu.duplicate": "Dupliceren", "CardBadges.title-checkboxes": "Selectievakjes", "CardBadges.title-comments": "Opmerkingen", "CardBadges.title-description": "Deze kaart heeft een beschrijving", @@ -68,20 +77,21 @@ "CardDetail.add-icon": "Pictogram toevoegen", "CardDetail.add-property": "+ Een eigenschap toevoegen", "CardDetail.addCardText": "kaarttekst toevoegen", - "CardDetail.moveContent": "inhoud van de kaart verplaatsen", + "CardDetail.limited-body": "Upgrade naar ons Professional- of Enterprise-plan om gearchiveerde kaarten te bekijken, ongelimiteerde weergaven per bord, ongelimiteerde kaarten en meer.", + "CardDetail.limited-button": "Upgraden", + "CardDetail.limited-title": "Deze kaart is verborgen", + "CardDetail.moveContent": "Inhoud van de kaart verplaatsen", "CardDetail.new-comment-placeholder": "Voeg commentaar toe...", "CardDetailProperty.confirm-delete-heading": "Bevestig verwijderen eigenschap", "CardDetailProperty.confirm-delete-subtext": "Weet je zeker dat je de eigenschap \"{propertyName}\" wilt verwijderen? Dit verwijderen zal de eigenschap van alle kaarten in dit bord verwijderen.", "CardDetailProperty.confirm-property-name-change-subtext": "Weet je zeker dat je de eigenschap \"{propertyName}\" {customText} wilt wijzigen? Dit zal invloed hebben op de waarde(n) op de {numOfCards} kaart(en) in dit bord, en kan resulteren in data verlies.", - "CardDetailProperty.confirm-property-type-change": "Bevestig Wijziging Type Eigenschap!", + "CardDetailProperty.confirm-property-type-change": "Bevestig wijziging type eigenschap", "CardDetailProperty.delete-action-button": "Verwijderen", "CardDetailProperty.property-change-action-button": "Wijzig eigenschap", "CardDetailProperty.property-changed": "Eigenschap succesvol gewijzigd!", "CardDetailProperty.property-deleted": "{propertyName} werd succesvol verwijderd!", "CardDetailProperty.property-name-change-subtext": "type van \"{oldPropType}\" naar \"{newPropType}\"", - "CardDetailProperty.property-type-change-subtext": "hernoem naar \"{newPropName}\"", - "CardDialog.copiedLink": "Gekopieerd!", - "CardDialog.copyLink": "Kopieer link", + "CardDetial.limited-link": "Meer informatie over onze plannen.", "CardDialog.delete-confirmation-dialog-button-text": "Verwijderen", "CardDialog.delete-confirmation-dialog-heading": "Bevestig kaart verwijderen!", "CardDialog.editing-template": "Je bent een sjabloon aan het bewerken.", @@ -92,6 +102,7 @@ "Categories.CreateCategoryDialog.UpdateText": "Bijwerken", "CenterPanel.Login": "Aanmelden", "CenterPanel.Share": "Delen", + "CloudMessage.cloud-server": "Krijg jouw eigen gratis cloud server.", "ColorOption.selectColor": "Selecteer {color} Kleur", "Comment.delete": "Verwijderen", "CommentsList.send": "Verzenden", @@ -118,7 +129,8 @@ "DeleteBoardDialog.confirm-cancel": "Annuleren", "DeleteBoardDialog.confirm-delete": "Verwijderen", "DeleteBoardDialog.confirm-info": "Weet je zeker dat u het bord \"{boardTitle}\" wil verwijderen? Het verwijderen van het bord zal alle kaarten in het bord verwijderen.", - "DeleteBoardDialog.confirm-tite": "Bevestig verwijderen bord", + "DeleteBoardDialog.confirm-info-template": "Weet je zeker dat je het boardsjabloon \"{boardTitle}\" wilt verwijderen?", + "DeleteBoardDialog.confirm-tite": "Bevestig verwijderen board", "DeleteBoardDialog.confirm-tite-template": "Bevestig verwijderen Board-sjabloon", "Dialog.closeDialog": "Dialoogvenster sluiten", "EditableDayPicker.today": "Vandaag", @@ -130,23 +142,17 @@ "Filter.not-includes": "bevat niet", "FilterComponent.add-filter": "+ Filter toevoegen", "FilterComponent.delete": "Verwijderen", - "FindBoFindBoardsDialog.IntroText": "Zoeken naar borden", + "FindBoardsDialog.IntroText": "Zoeken naar borden", "FindBoardsDialog.NoResultsFor": "Geen resultaten voor \"{searchQuery}\"", "FindBoardsDialog.NoResultsSubtext": "Controleer de spelling of probeer een andere zoekopdracht.", "FindBoardsDialog.SubTitle": "Typ om een bord te vinden. Gebruik UP/DOWN om te bladeren. ENTER om te selecteren, ESC om te annuleren", "FindBoardsDialog.Title": "Boards vinden", - "GalleryCard.copiedLink": "Gekopieerd!", - "GalleryCard.copyLink": "Kopieer link", - "GalleryCard.delete": "Verwijderen", - "GalleryCard.duplicate": "Kopiëren", "GroupBy.hideEmptyGroups": "Verberg {count} lege groepen", "GroupBy.showHiddenGroups": "Toon {count} verborgen groepen", "GroupBy.ungroup": "Groeperen stoppen", - "KanbanCard.copiedLink": "Gekopieerd!", - "KanbanCard.copyLink": "Kopieer link", - "KanbanCard.delete": "Verwijderen", - "KanbanCard.duplicate": "Kopiëren", + "HideBoard.MenuOption": "Bord verbergen", "KanbanCard.untitled": "Titelloos", + "Mutator.new-board-from-template": "nieuw board van sjabloon", "Mutator.new-card-from-template": "nieuwe kaart van sjabloon", "Mutator.new-template-from-card": "nieuw sjabloon van kaart", "OnboardingTour.AddComments.Body": "Je kunt commentaar geven op onderwerpen, en zelfs je medeMattermostgebruikers @vermelden om hun aandacht te trekken.", @@ -172,7 +178,7 @@ "PropertyType.CreatedTime": "Aangemaakt op", "PropertyType.Date": "Datum", "PropertyType.Email": "E-mail", - "PropertyType.File": "Bestand of Media", + "PropertyType.File": "Bestand of media", "PropertyType.MultiSelect": "Multiselect", "PropertyType.Number": "Nummer", "PropertyType.Person": "Persoon", @@ -189,15 +195,16 @@ "RegistrationLink.description": "Deel deze link zodat anderen een account kunnen aanmaken:", "RegistrationLink.regenerateToken": "Token opnieuw genereren", "RegistrationLink.tokenRegenerated": "Registratielink heraangemaakt", - "ShareBoard.PublishDescription": "Publiceer en deel een \"alleen-lezen\" link met iedereen op het web", + "ShareBoard.PublishDescription": "Publiceer en deel een \"alleen-lezen\" link met iedereen op het web.", "ShareBoard.PublishTitle": "Publiceren op het web", "ShareBoard.ShareInternal": "Intern delen", - "ShareBoard.ShareInternalDescription": "Gebruikers die toegangsrechten hebben, kunnen deze link gebruiken", + "ShareBoard.ShareInternalDescription": "Gebruikers die toegangsrechten hebben, kunnen deze link gebruiken.", "ShareBoard.Title": "Bord delen", "ShareBoard.confirmRegenerateToken": "Dit zal eerder gedeelde links ongeldig maken. Doorgaan?", "ShareBoard.copiedLink": "Gekopieerd!", "ShareBoard.copyLink": "Link kopiëren", "ShareBoard.regenerate": "Token opnieuw genereren", + "ShareBoard.searchPlaceholder": "Mensen en kanalen zoeken", "ShareBoard.teamPermissionsText": "Iedereen van team {teamName}", "ShareBoard.tokenRegenrated": "Token opnieuw gegenereerd", "ShareBoard.userPermissionsRemoveMemberText": "Lid verwijderen", @@ -244,9 +251,16 @@ "URLProperty.copiedLink": "Gekopieerd!", "URLProperty.copy": "Kopiëren", "URLProperty.edit": "Bewerken", + "UndoRedoHotKeys.canRedo": "Herhaal", + "UndoRedoHotKeys.canRedo-with-description": "Herhaal {description}", + "UndoRedoHotKeys.canUndo": "Ongedaan maken", + "UndoRedoHotKeys.canUndo-with-description": "Ongedaan maken van {description}", + "UndoRedoHotKeys.cannotRedo": "Niets om te herhalen", + "UndoRedoHotKeys.cannotUndo": "Niets om ongedaan te maken", "ValueSelector.noOptions": "Geen opties. Begin te typen om de eerste toe te voegen!", "ValueSelector.valueSelector": "Waardekiezer", "ValueSelectorLabel.openMenu": "Menu openen", + "VersionMessage.help": "Bekijk eens wat nieuw is in deze versie.", "View.AddView": "Weergave toevoegen", "View.Board": "Bord", "View.DeleteView": "Weergave verwijderen", @@ -256,6 +270,7 @@ "View.NewCalendarTitle": "Kalenderweergave", "View.NewGalleryTitle": "Galerie bekijken", "View.NewTableTitle": "Tabelweergave", + "View.NewTemplateTitle": "Naamloos sjabloon", "View.Table": "Tabel", "ViewHeader.add-template": "Nieuw sjabloon", "ViewHeader.delete-template": "Verwijderen", @@ -271,7 +286,6 @@ "ViewHeader.new": "Nieuw", "ViewHeader.properties": "Eigenschappen", "ViewHeader.properties-menu": "Eigenschappen-menu", - "ViewHeader.search": "Zoeken", "ViewHeader.search-text": "Kaarten zoeken", "ViewHeader.select-a-template": "Kies een sjabloon", "ViewHeader.set-default-template": "Instellen als standaard", @@ -279,34 +293,82 @@ "ViewHeader.untitled": "Titelloos", "ViewHeader.view-header-menu": "Menu hoofding weergeven", "ViewHeader.view-menu": "Menuweergave", + "ViewLimitDialog.Heading": "Limiet aantal views per board bereikt", + "ViewLimitDialog.PrimaryButton.Title.Admin": "Upgraden", + "ViewLimitDialog.PrimaryButton.Title.RegularUser": "Verwittig Admin", + "ViewLimitDialog.Subtext.Admin": "Upgrade naar ons Professional- of Enterprise-plan om gearchiveerde kaarten te bekijken, ongelimiteerde weergaven per bord, ongelimiteerde kaarten en meer.", + "ViewLimitDialog.Subtext.Admin.PricingPageLink": "Meer informatie over onze plannen.", + "ViewLimitDialog.Subtext.RegularUser": "Verwittig jouw Admin om te upgraden naar ons Professioneel of Enterprise plan om onbeperkte weergaven per bord, onbeperkte kaarten, en meer te hebben.", + "ViewLimitDialog.UpgradeImg.AltText": "upgrade afbeelding", + "ViewLimitDialog.notifyAdmin.Success": "Jouw beheerder is op de hoogte gebracht", "ViewTitle.hide-description": "beschrijving verbergen", "ViewTitle.pick-icon": "Pictogram kiezen", "ViewTitle.random-icon": "Willekeurig", "ViewTitle.remove-icon": "Verwijder pictogram", "ViewTitle.show-description": "beschrijving tonen", "ViewTitle.untitled-board": "Titelloos board", - "WelcomePage.Description": "Boards is een projectmanagementtool die helpt bij het definiëren, organiseren, volgen en beheren van werk door teams heen, met behulp van een bekende kanban-bordweergave", + "WelcomePage.Description": "Boards is een projectmanagementtool die helpt bij het definiëren, organiseren, volgen en beheren van werk door teams heen, met behulp van een bekende Kanban-bordweergave.", "WelcomePage.Explore.Button": "Start een rondleiding", "WelcomePage.Heading": "Welkom bij Boards", "WelcomePage.NoThanks.Text": "Nee bedankt, ik zoek het zelf wel uit", "Workspace.editing-board-template": "Je bent een bordsjabloon aan het bewerken.", + "boardSelector.confirm-link-board": "Koppel bord aan kanaal", + "boardSelector.confirm-link-board-button": "Bord koppelen", + "boardSelector.confirm-link-board-subtext": "Het linken van het \"{boardName}\" bord naar dit kanaal zou alle leden van dit kanaal \"Editor\"-toegang geven tot het bord. Weet je zeker dat je het wilt linken?", + "boardSelector.create-a-board": "Maak een bord", + "boardSelector.link": "Link", + "boardSelector.search-for-boards": "Zoeken naar borden", + "boardSelector.title": "Link borden", + "boardSelector.unlink": "Link ongedaan maken", "calendar.month": "Maand", "calendar.today": "VANDAAG", "calendar.week": "Week", + "cloudMessage.learn-more": "Meer info", "createImageBlock.failed": "Kan het bestand niet uploaden. Limiet bestandsgrootte bereikt.", "default-properties.badges": "Opmerkingen en beschrijving", "default-properties.title": "Titel", + "error.back-to-home": "Terug naar startpagina", + "error.back-to-team": "Terug naar team", + "error.board-not-found": "Board niet gevonden.", + "error.go-login": "Aanmelden", + "error.invalid-read-only-board": "Je hebt geen toegang tot dit board. Meld je aan om toegang te krijgen tot Boards.", + "error.not-logged-in": "Jouw sessie is misschien verlopen of je bent niet ingelogd. Meldt je opnieuw aan om toegang te krijgen tot Boards.", "error.page.title": "Sorry, er ging iets mis", + "error.team-undefined": "Geen geldig team.", + "error.unknown": "Er trad een fout op.", "generic.previous": "Vorige", "imagePaste.upload-failed": "Sommige bestanden niet geupload. Limiet bestandsgrootte bereikt", + "limitedCard.title": "Verborgen kaarten", "login.log-in-button": "Aanmelden", "login.log-in-title": "Aanmelden", "login.register-button": "of maak een account aan als je er nog geen hebt", + "notification-box-card-limit-reached.close-tooltip": "Snooze voor 10 dagen", + "notification-box-card-limit-reached.contact-link": "breng je beheerder op de hoogte", + "notification-box-card-limit-reached.link": "Upgrade naar een betaald plan", + "notification-box-card-limit-reached.title": "{cards} kaarten verborgen van board", + "notification-box-cards-hidden.title": "Deze actie heeft een andere kaart verborgen", + "notification-box.card-limit-reached.not-admin.text": "Om toegang te krijgen tot gearchiveerde kaarten, neem contact op met {contactLink} om te upgraden naar een betaald plan.", + "notification-box.card-limit-reached.text": "Limiet van aantal kaarten bereikt, om oudere kaarten te bekijken, {link}", "register.login-button": "of meldt je aan als je al een account hebt", "register.signup-title": "Maak een nieuw account", + "rhs-boards.add": "Toevoegen", + "rhs-boards.dm": "DM", + "rhs-boards.gm": "GM", + "rhs-boards.header.dm": "dit directe bericht", + "rhs-boards.header.gm": "dit groepsbericht", + "rhs-boards.last-update-at": "Laatste wijziging op: {datetime}", + "rhs-boards.link-boards-to-channel": "Koppel borden aan {channelName}", + "rhs-boards.linked-boards": "Gekoppelde borden", + "rhs-boards.no-boards-linked-to-channel": "Er zijn nog geen borden gekoppeld aan {channelName}", + "rhs-boards.no-boards-linked-to-channel-description": "Boards is een projectmanagementtool die helpt bij het definiëren, organiseren, volgen en beheren van werk door teams heen, met behulp van een bekende kanban-bordweergave.", + "rhs-boards.unlink-board": "Bord loskoppelen", + "rhs-channel-boards-header.title": "Boards", "share-board.publish": "Publiceren", "share-board.share": "Delen", + "shareBoard.channels-select-group": "Kanalen", + "shareBoard.confirm-link-public-channel-button": "Ja, voeg publiek kanaal toe", "shareBoard.lastAdmin": "Besturen moeten ten minste één beheerder hebben", + "shareBoard.members-select-group": "Leden", "tutorial_tip.finish_tour": "Klaar", "tutorial_tip.got_it": "Begrepen", "tutorial_tip.ok": "Volgende", diff --git a/webapp/i18n/oc.json b/webapp/i18n/oc.json index 90420dc75..6c43a3e79 100644 --- a/webapp/i18n/oc.json +++ b/webapp/i18n/oc.json @@ -18,8 +18,6 @@ "CardDetail.new-comment-placeholder": "Apondre un comentari...", "CardDetailProperty.confirm-delete-subtext": "Volètz vertadièrament suprimir la proprietat « {propertyName} » ? La supression levarà la proprietat de totas las cartas d’aquesta tablèu.", "CardDetailProperty.property-deleted": "Supression de {propertyName} reüssida !", - "CardDialog.copiedLink": "Copiat !", - "CardDialog.copyLink": "Copiar ligam", "CardDialog.editing-template": "Sètz a modificar un modèl.", "CardDialog.nocard": "Aquesta zòna existís pas o es pas accessibla.", "ColorOption.selectColor": "Seleccionar la color {color}", @@ -49,15 +47,7 @@ "Filter.not-includes": "inclutz pas", "FilterComponent.add-filter": "+ Apondre un filtre", "FilterComponent.delete": "Suprimir", - "GalleryCard.copiedLink": "Copiat !", - "GalleryCard.copyLink": "Copiar ligam", - "GalleryCard.delete": "Suprimir", - "GalleryCard.duplicate": "Duplicar", "GroupBy.ungroup": "Desgropar", - "KanbanCard.copiedLink": "Copiat !", - "KanbanCard.copyLink": "Copiar lo ligam", - "KanbanCard.delete": "Suprimir", - "KanbanCard.duplicate": "Duplicar", "KanbanCard.untitled": "Sens títol", "Mutator.new-card-from-template": "zòna novèla a partir d’un modèl", "Mutator.new-template-from-card": "modèl novèl a partir d’una zòna", @@ -139,7 +129,6 @@ "ViewHeader.group-by": "Agropar per : {property}", "ViewHeader.new": "Novèl", "ViewHeader.properties": "Proprietats", - "ViewHeader.search": "Recercar", "ViewHeader.search-text": "Recercar de tèxt", "ViewHeader.select-a-template": "Seleccionar un modèl", "ViewHeader.set-default-template": "Definir per defaut", diff --git a/webapp/i18n/pl.json b/webapp/i18n/pl.json index f76b20a6d..285d98619 100644 --- a/webapp/i18n/pl.json +++ b/webapp/i18n/pl.json @@ -8,21 +8,25 @@ "BoardComponent.no-property-title": "Elementy z pustą właściwością {property} trafią tutaj. Tej kolumny nie można usunąć.", "BoardComponent.show": "Pokaż", "BoardMember.schemeAdmin": "Administrator", + "BoardMember.schemeCommenter": "Komentujący", "BoardMember.schemeEditor": "Redaktor", "BoardMember.schemeNone": "Brak", "BoardMember.schemeViewer": "Przeglądarka", + "BoardMember.schemeViwer": "Przeglądający", + "BoardMember.unlinkChannel": "Odłącz", "BoardPage.newVersion": "Nowa wersja tablic jest dostępna, kliknij tutaj, aby przeładować.", "BoardPage.syncFailed": "Tablica może zostać usunięta lub dostęp do niej cofnięty.", "BoardTemplateSelector.add-template": "Nowy szablon", "BoardTemplateSelector.create-empty-board": "Utwórz pustą tablicę", "BoardTemplateSelector.delete-template": "Usuń", - "BoardTemplateSelector.description": "Wybierz szablon, który pomoże Ci zacząć. Z łatwością dostosuj szablon do swoich potrzeb lub utwórz pustą tablicę, aby zacząć od zera.", + "BoardTemplateSelector.description": "Dodaj tablicę do paska bocznego, używając dowolnego z szablonów zdefiniowanych poniżej lub zacznij od zera.", "BoardTemplateSelector.edit-template": "Edycja", "BoardTemplateSelector.plugin.no-content-description": "Dodaj tablicę do paska bocznego używając jednego z szablonów zdefiniowanych poniżej lub zacznij od zera.{lineBreak} Członkowie \"{teamName}\" będą mieli dostęp do tablic utworzonych tutaj.", "BoardTemplateSelector.plugin.no-content-title": "Utwórz tablicę w {teamName}", - "BoardTemplateSelector.title": "Utwórz Tablicę", + "BoardTemplateSelector.title": "Utwórz tablicę", "BoardTemplateSelector.use-this-template": "Użyj tego szablonu", "BoardsSwitcher.Title": "Znajdź Tablice", + "BoardsUnfurl.Limited": "Dodatkowe szczegóły są ukryte ze względu na archiwizację karty", "BoardsUnfurl.Remainder": "+{remainder} więcej", "BoardsUnfurl.Updated": "Zaktualizowane {time}", "Calculations.Options.average.displayName": "Średni", @@ -30,13 +34,13 @@ "Calculations.Options.count.displayName": "Licznik", "Calculations.Options.count.label": "Licznik", "Calculations.Options.countChecked.displayName": "Zaznaczone", - "Calculations.Options.countChecked.label": "Zaznaczono Licznik", + "Calculations.Options.countChecked.label": "Zaznaczono licznik", "Calculations.Options.countUnchecked.displayName": "Niezaznaczony", - "Calculations.Options.countUnchecked.label": "Licznik Niezaznaczony", + "Calculations.Options.countUnchecked.label": "Licznik niezaznaczony", "Calculations.Options.countUniqueValue.displayName": "Unikalny", - "Calculations.Options.countUniqueValue.label": "Licznik Unikalnych Wartości", + "Calculations.Options.countUniqueValue.label": "Licznik unikalnych wartości", "Calculations.Options.countValue.displayName": "Wartości", - "Calculations.Options.countValue.label": "Licznik Wartości", + "Calculations.Options.countValue.label": "Licznik wartości", "Calculations.Options.dateRange.displayName": "Zakres", "Calculations.Options.dateRange.label": "Zakres", "Calculations.Options.earliest.displayName": "Wcześniejszy", @@ -52,13 +56,18 @@ "Calculations.Options.none.displayName": "Policz", "Calculations.Options.none.label": "Żaden", "Calculations.Options.percentChecked.displayName": "Zaznaczony", - "Calculations.Options.percentChecked.label": "Zaznaczono Procent", + "Calculations.Options.percentChecked.label": "Procent sprawdzonych", "Calculations.Options.percentUnchecked.displayName": "Niezaznaczony", - "Calculations.Options.percentUnchecked.label": "Niezaznaczono Procent", + "Calculations.Options.percentUnchecked.label": "Procent nie zaznaczonych", "Calculations.Options.range.displayName": "Zakres", "Calculations.Options.range.label": "Zakres", "Calculations.Options.sum.displayName": "Suma", "Calculations.Options.sum.label": "Suma", + "CalendarCard.untitled": "Bez tytułu", + "CardActionsMenu.copiedLink": "Skopiowane!", + "CardActionsMenu.copyLink": "Kopiuj odnośnik", + "CardActionsMenu.delete": "Usuń", + "CardActionsMenu.duplicate": "Duplikuj", "CardBadges.title-checkboxes": "Pola wyboru", "CardBadges.title-comments": "Komentarze", "CardBadges.title-description": "Ta karta ma opis", @@ -68,20 +77,21 @@ "CardDetail.add-icon": "Dodaj ikonę", "CardDetail.add-property": "+ Dodaj właściwość", "CardDetail.addCardText": "dodaj tekst karty", - "CardDetail.moveContent": "przenieś zawartość karty", + "CardDetail.limited-body": "Uaktualnij plan do wersji Professional lub Enterprise, aby przeglądać zarchiwizowane karty, mieć nieograniczoną liczbę odsłon na tablice, nieograniczoną liczbę kart i wiele więcej.", + "CardDetail.limited-button": "Aktualizuj", + "CardDetail.limited-title": "Ta karta jest ukryta", + "CardDetail.moveContent": "Przenieś zawartość karty", "CardDetail.new-comment-placeholder": "Dodaj komentarz...", - "CardDetailProperty.confirm-delete-heading": "Potwierdź Usunięcie Własności", + "CardDetailProperty.confirm-delete-heading": "Potwierdź usunięcie własności", "CardDetailProperty.confirm-delete-subtext": "Czy na pewno chcesz usunąć właściwość \"{propertyName}\"? Usunięcie tej właściwości spowoduje usunięcie jej z wszystkich kart na tej tablicy.", "CardDetailProperty.confirm-property-name-change-subtext": "Czy na pewno chcesz zmienić właściwość \"{propertyName}\" {customText}? Wpłynie to na wartości na kartach {numOfCards} na tej tablicy i może spowodować utratę danych.", - "CardDetailProperty.confirm-property-type-change": "Potwierdź Zmianę Typu Właściwości!", + "CardDetailProperty.confirm-property-type-change": "Potwierdź zmianę typu właściwości", "CardDetailProperty.delete-action-button": "Usuń", - "CardDetailProperty.property-change-action-button": "Zmień Właściwość", + "CardDetailProperty.property-change-action-button": "Zmień właściwość", "CardDetailProperty.property-changed": "Zmieniono właściwość pomyślnie!", "CardDetailProperty.property-deleted": "Usunięto pomyślnie {propertyName}!", "CardDetailProperty.property-name-change-subtext": "typ z \"{oldPropType}\" do \"{newPropType}\"", - "CardDetailProperty.property-type-change-subtext": "nazwę do \"{newPropName}\"", - "CardDialog.copiedLink": "Skopiowane!", - "CardDialog.copyLink": "Kopiuj odnośnik", + "CardDetial.limited-link": "Dowiedz się więcej o naszych planach.", "CardDialog.delete-confirmation-dialog-button-text": "Usuń", "CardDialog.delete-confirmation-dialog-heading": "Potwierdź usunięcie karty!", "CardDialog.editing-template": "Edytujesz szablon.", @@ -92,6 +102,7 @@ "Categories.CreateCategoryDialog.UpdateText": "Zaktualizuj", "CenterPanel.Login": "Logowanie", "CenterPanel.Share": "Udostępnij", + "CloudMessage.cloud-server": "Uzyskaj własny, bezpłatny serwer w chmurze.", "ColorOption.selectColor": "Wybierz {color} Kolor", "Comment.delete": "Usuń", "CommentsList.send": "Wyślij", @@ -118,8 +129,9 @@ "DeleteBoardDialog.confirm-cancel": "Anuluj", "DeleteBoardDialog.confirm-delete": "Usuń", "DeleteBoardDialog.confirm-info": "Czy na pewno chcesz usunąć tablicę \"{boardTitle}\"? Usunięcie jej spowoduje usunięcie wszystkich kart na tablicy.", - "DeleteBoardDialog.confirm-tite": "Potwierdź Usunięcie Tablicy", - "DeleteBoardDialog.confirm-tite-template": "Potwierdź usunięcie Szablonu Tablicy", + "DeleteBoardDialog.confirm-info-template": "Czy na pewno chcesz usunąć szablon tablicy \"{boardTitle}\"?", + "DeleteBoardDialog.confirm-tite": "Potwierdź usunięcie tablicy", + "DeleteBoardDialog.confirm-tite-template": "Potwierdź usunięcie szablonu tablicy", "Dialog.closeDialog": "Zamknij okno dialogowe", "EditableDayPicker.today": "Dzisiaj", "Error.mobileweb": "Obsługa mobilnej strony internetowej jest obecnie we wczesnej wersji beta. Nie wszystkie funkcje mogą być dostępne.", @@ -130,23 +142,16 @@ "Filter.not-includes": "nie zawiera", "FilterComponent.add-filter": "+ Dodaj filtr", "FilterComponent.delete": "Usuń", - "FindBoFindBoardsDialog.IntroText": "Wyszukiwanie tablic", + "FindBoardsDialog.IntroText": "Wyszukiwanie tablic", "FindBoardsDialog.NoResultsFor": "Brak wyników dla \"{searchQuery}\"", "FindBoardsDialog.NoResultsSubtext": "Sprawdź pisownię lub spróbuj innego wyszukiwania.", "FindBoardsDialog.SubTitle": "Wpisz, aby znaleźć tablicę. Użyj GÓRA/DÓŁ, aby przeglądać. ENTER, aby wybrać, ESC, aby odrzucić", "FindBoardsDialog.Title": "Znajdź Tablice", - "GalleryCard.copiedLink": "Skopiowane!", - "GalleryCard.copyLink": "Kopiuj odnośnik", - "GalleryCard.delete": "Usuń", - "GalleryCard.duplicate": "Duplikuj", "GroupBy.hideEmptyGroups": "Ukryj {count} pustych grup", "GroupBy.showHiddenGroups": "Pokaż {count} ukrytych grup", "GroupBy.ungroup": "Rozgrupuj", - "KanbanCard.copiedLink": "Skopiowane!", - "KanbanCard.copyLink": "Kopiuj odnośnik", - "KanbanCard.delete": "Usuń", - "KanbanCard.duplicate": "Duplikuj", "KanbanCard.untitled": "Bez tytułu", + "Mutator.new-board-from-template": "nowa tablica z szablonu", "Mutator.new-card-from-template": "nowa karta z szablonu", "Mutator.new-template-from-card": "nowy szablon z karty", "OnboardingTour.AddComments.Body": "Możesz komentować wydarzenia, a nawet @mention innych użytkowników Mattermost, aby zwrócić na siebie ich uwagę.", @@ -157,7 +162,7 @@ "OnboardingTour.AddProperties.Title": "Dodaj właściwości", "OnboardingTour.AddView.Body": "Przejdź tutaj, aby utworzyć nowy widok w celu uporządkowania tablicy przy użyciu różnych układów.", "OnboardingTour.AddView.Title": "Dodaj nowy widok", - "OnboardingTour.CopyLink.Body": "Karty można udostępniać kolegom z zespołu, kopiując łącze i wklejając je w kanale, Wiadomości Bezpośredniej lub Wiadomości Grupowej.", + "OnboardingTour.CopyLink.Body": "Karty można udostępniać kolegom z zespołu, kopiując łącze i wklejając je w kanale, wiadomości bezpośredniej lub wiadomości grupowej.", "OnboardingTour.CopyLink.Title": "Kopiuj odnośnik", "OnboardingTour.OpenACard.Body": "Otwórz kartę, aby poznać różne sposoby, w których Tablice mogą pomóc Ci w organizacji pracy.", "OnboardingTour.OpenACard.Title": "Otwórz kartę", @@ -172,7 +177,7 @@ "PropertyType.CreatedTime": "Czas utworzenia", "PropertyType.Date": "Data", "PropertyType.Email": "Email", - "PropertyType.File": "Plik lub Media", + "PropertyType.File": "Plik lub media", "PropertyType.MultiSelect": "Multiwybór", "PropertyType.Number": "Numer", "PropertyType.Person": "Osoba", @@ -189,15 +194,16 @@ "RegistrationLink.description": "Udostępnij ten link, aby inni mogli utworzyć konta:", "RegistrationLink.regenerateToken": "Wygeneruj ponownie token", "RegistrationLink.tokenRegenerated": "Link rejestracyjny wygenerowany", - "ShareBoard.PublishDescription": "Publikowanie i udostępnianie linku \"tylko do odczytu\" wszystkim w sieci", + "ShareBoard.PublishDescription": "Publikowanie i udostępnianie linku tylko-do-odczyt\" wszystkim w sieci.", "ShareBoard.PublishTitle": "Opublikuj w sieci", "ShareBoard.ShareInternal": "Udostępnij wewnętrznie", - "ShareBoard.ShareInternalDescription": "Użytkownicy z odpowiednimi uprawnieniami będą mogli korzystać z tego łącza", + "ShareBoard.ShareInternalDescription": "Użytkownicy z odpowiednimi uprawnieniami będą mogli korzystać z tego łącza.", "ShareBoard.Title": "Udostępnij Tablicę", "ShareBoard.confirmRegenerateToken": "Spowoduje to unieważnienie wcześniej udostępnionych linków. Kontynuować?", "ShareBoard.copiedLink": "Skopiowane!", "ShareBoard.copyLink": "Kopiuj odnośnik", "ShareBoard.regenerate": "Wygeneruj ponownie token", + "ShareBoard.searchPlaceholder": "Wyszukiwanie osób", "ShareBoard.teamPermissionsText": "Wszyscy w Zespole {teamName}", "ShareBoard.tokenRegenrated": "Token wygenerowany", "ShareBoard.userPermissionsRemoveMemberText": "Usuń użytkownika", @@ -256,6 +262,7 @@ "View.NewCalendarTitle": "Widok Kalendarza", "View.NewGalleryTitle": "Widok galerii", "View.NewTableTitle": "Widok tabeli", + "View.NewTemplateTitle": "Szablon bez tytułu", "View.Table": "Tabela", "ViewHeader.add-template": "Nowy szablon", "ViewHeader.delete-template": "Usuń", @@ -271,7 +278,6 @@ "ViewHeader.new": "Nowy", "ViewHeader.properties": "Właściwości", "ViewHeader.properties-menu": "Menu właściwości", - "ViewHeader.search": "Szukaj", "ViewHeader.search-text": "Przeszukaj karty", "ViewHeader.select-a-template": "Wybierz szablon", "ViewHeader.set-default-template": "Ustaw jako domyślne", @@ -279,34 +285,84 @@ "ViewHeader.untitled": "Bez tytułu", "ViewHeader.view-header-menu": "Wyświetl menu nagłówka", "ViewHeader.view-menu": "Wyświetl menu", + "ViewLimitDialog.Heading": "Osiągnięty limit odsłon na tablicę", + "ViewLimitDialog.PrimaryButton.Title.Admin": "Aktualizuj", + "ViewLimitDialog.PrimaryButton.Title.RegularUser": "Powiadom Administratora", + "ViewLimitDialog.Subtext.Admin": "Uaktualnij do naszego planu Professional lub Enterprise, aby mieć nieograniczone widoki na tablice, nieograniczone karty i więcej.", + "ViewLimitDialog.Subtext.Admin.PricingPageLink": "Dowiedz się więcej o naszych planach.", + "ViewLimitDialog.Subtext.RegularUser": "Powiadom swojego Administratora, aby uaktualnić do naszego planu Professional lub Enterprise, aby mieć nieograniczone widoki na tablicach, nieograniczone karty i więcej.", + "ViewLimitDialog.UpgradeImg.AltText": "aktualizuj obraz", + "ViewLimitDialog.notifyAdmin.Success": "Twój administrator został powiadomiony", "ViewTitle.hide-description": "ukryj opis", "ViewTitle.pick-icon": "Wybierz ikonę", "ViewTitle.random-icon": "Losowy", "ViewTitle.remove-icon": "Usuń ikonę", "ViewTitle.show-description": "pokaż opis", - "ViewTitle.untitled-board": "Tablica bez Tytułu", - "WelcomePage.Description": "Tablice to narzędzie do zarządzania projektami, które pomaga definiować, organizować, śledzić i zarządzać pracą w zespołach, wykorzystując widok znanych tablic kanban", + "ViewTitle.untitled-board": "Tablica bez tytułu", + "WelcomePage.Description": "Tablice to narzędzie do zarządzania projektami, które pomaga definiować, organizować, śledzić i zarządzać pracą w zespołach wykorzystując widok znanych tablic Kanban.", "WelcomePage.Explore.Button": "Wybierz się na wycieczkę", "WelcomePage.Heading": "Witamy w Tablicach", "WelcomePage.NoThanks.Text": "Nie, dzięki, sam sobie z tym poradzę", "Workspace.editing-board-template": "Edytujesz szablon tablicy.", + "boardSelector.confirm-link-board": "Połączenie tablicy z kanałem", + "boardSelector.confirm-link-board-button": "Tak, podlinkuj tablicę", + "boardSelector.confirm-link-board-subtext": "Powiązanie \"{boardName}\" tablicy z tym kanałem dałoby wszystkim członkom tego kanału \"Editor\" dostęp do tablicy. Czy na pewno chcesz to połączyć?", + "boardSelector.create-a-board": "Utwórz tablicę", + "boardSelector.link": "Link", + "boardSelector.search-for-boards": "Wyszukiwanie tablic", + "boardSelector.title": "Linki tablic", + "boardSelector.unlink": "Odłącz", "calendar.month": "Miesiąc", "calendar.today": "DZIŚ", "calendar.week": "Tydzień", + "cloudMessage.learn-more": "Dowiedź się więcej", "createImageBlock.failed": "Nie można przesłać pliku. Osiągnięto limit rozmiaru pliku.", "default-properties.badges": "Uwagi i opis", "default-properties.title": "Tytuł", + "error.back-to-home": "Powrót na stronę główną", + "error.back-to-team": "Powrót do zespołu", + "error.board-not-found": "Nie znaleziono tablicy.", + "error.go-login": "Zaloguj się", + "error.invalid-read-only-board": "Nie masz dostępu do tej tablicy. Zaloguj się, aby uzyskać dostęp do tablic.", + "error.not-logged-in": "Twoja sesja mogła wygasnąć lub nie jesteś zalogowany. Zaloguj się ponownie, aby uzyskać dostęp do Tablic.", "error.page.title": "Przepraszam, coś poszło nie tak", + "error.team-undefined": "Nieprawidłowy zespół.", + "error.unknown": "Wystąpił błąd.", "generic.previous": "Wstecz", "imagePaste.upload-failed": "Niektóre pliki nie zostały przesłane. Osiągnięto limit rozmiaru pliku", + "limitedCard.title": "Ukryte karty", "login.log-in-button": "Zaloguj się", "login.log-in-title": "Zaloguj się", "login.register-button": "lub załóż konto, jeśli jeszcze go nie masz", + "notification-box-card-limit-reached.close-tooltip": "Uśpij na 10 dni", + "notification-box-card-limit-reached.contact-link": "powiadom swojego administratora", + "notification-box-card-limit-reached.link": "Uaktualnienie do planu płatnego", + "notification-box-card-limit-reached.title": "{cards} karty ukryte z tablicy", + "notification-box-cards-hidden.title": "Ta akcja zakryła inną kartę", + "notification-box.card-limit-reached.not-admin.text": "Aby uzyskać dostęp do zarchiwizowanych kart, możesz {contactLink} uaktualnić do płatnego planu.", + "notification-box.card-limit-reached.text": "Osiągnięto limit kart, aby wyświetlić starsze karty, {link}", "register.login-button": "lub zaloguj się, jeśli masz już konto", "register.signup-title": "Zarejestruj się na swoim koncie", + "rhs-boards.add": "Dodaj", + "rhs-boards.dm": "DM", + "rhs-boards.gm": "GM", + "rhs-boards.header.dm": "ta Bezpośrednia Wiadomość", + "rhs-boards.header.gm": "ta Wiadomość Grupowa", + "rhs-boards.last-update-at": "Ostatnia aktualizacja o: {datetime}", + "rhs-boards.link-boards-to-channel": "Połączenie tablic z {channelName}", + "rhs-boards.linked-boards": "Połączone tablice", + "rhs-boards.no-boards-linked-to-channel": "Żadna z tablic nie jest jeszcze połączona z {channelName}", + "rhs-boards.no-boards-linked-to-channel-description": "Tablice to narzędzie do zarządzania projektami, które pomagają definiować, organizować, śledzić i zarządzać pracą w zespołach, wykorzystując widok znanych tablic kanban.", + "rhs-boards.unlink-board": "Odłączenie tablicy", + "rhs-channel-boards-header.title": "Tablice", "share-board.publish": "Opublikuj", "share-board.share": "Udostępnij", + "shareBoard.channels-select-group": "Kanały", + "shareBoard.confirm-unlink.body": "Kiedy odłączysz kanał od tablicy, wszyscy członkowie kanału (istniejący i nowi) stracą do niego dostęp, chyba że otrzymają osobne pozwolenie. {lineBreak} Czy na pewno chcesz go odłączyć?", + "shareBoard.confirm-unlink.confirmBtnText": "Tak, odłącz", + "shareBoard.confirm-unlink.title": "Odłączenie kanału od tablicy", "shareBoard.lastAdmin": "Tablice muszą mieć co najmniej jednego Administratora", + "shareBoard.members-select-group": "Członkowie", "tutorial_tip.finish_tour": "Gotowe", "tutorial_tip.got_it": "Jasne", "tutorial_tip.ok": "Dalej", diff --git a/webapp/i18n/pt_BR.json b/webapp/i18n/pt_BR.json index 4e7a8e551..e6792d190 100644 --- a/webapp/i18n/pt_BR.json +++ b/webapp/i18n/pt_BR.json @@ -41,11 +41,7 @@ "Filter.not-includes": "Não inclui", "FilterComponent.add-filter": "+ Adicionar filtro", "FilterComponent.delete": "Deletar", - "GalleryCard.delete": "Deletar", - "GalleryCard.duplicate": "Duplicar", "GroupBy.ungroup": "Desagrupar", - "KanbanCard.delete": "Deletar", - "KanbanCard.duplicate": "Duplicar", "KanbanCard.untitled": "Sem nome", "Mutator.new-card-from-template": "novo card à partir de um template", "Mutator.new-template-from-card": "novo template à partir de um card", @@ -121,7 +117,6 @@ "ViewHeader.group-by": "Agrupar por: {property}", "ViewHeader.new": "Novo", "ViewHeader.properties": "Propriedades", - "ViewHeader.search": "Pesquisar", "ViewHeader.search-text": "Pesquisar texto", "ViewHeader.select-a-template": "Selecionar um modelo", "ViewHeader.sort": "Ordenar", diff --git a/webapp/i18n/ru.json b/webapp/i18n/ru.json index 965c65dc3..4f2a7238f 100644 --- a/webapp/i18n/ru.json +++ b/webapp/i18n/ru.json @@ -8,9 +8,12 @@ "BoardComponent.no-property-title": "Здесь будут элементы с пустым свойством {property}. Этот столбец не может быть удален.", "BoardComponent.show": "Показать", "BoardMember.schemeAdmin": "Администратор", + "BoardMember.schemeCommenter": "Комментатор", "BoardMember.schemeEditor": "Редактор", "BoardMember.schemeNone": "Никто", "BoardMember.schemeViewer": "Наблюдатель", + "BoardMember.schemeViwer": "Зритель", + "BoardMember.unlinkChannel": "Отключить", "BoardPage.newVersion": "Доступна новая версия Доски. Нажмите здесь, чтобы перезагрузить.", "BoardPage.syncFailed": "Доска может быть удалена или доступ аннулирован.", "BoardTemplateSelector.add-template": "Новый шаблон", @@ -23,6 +26,7 @@ "BoardTemplateSelector.title": "Создать Доску", "BoardTemplateSelector.use-this-template": "Использовать этот шаблон", "BoardsSwitcher.Title": "Найти доски", + "BoardsUnfurl.Limited": "Информация скрыта в связи с тем, что карточка находится в архиве", "BoardsUnfurl.Remainder": "+{remainder} ещё", "BoardsUnfurl.Updated": "Обновлено {time}", "Calculations.Options.average.displayName": "Среднее", @@ -59,6 +63,11 @@ "Calculations.Options.range.label": "Диапазон", "Calculations.Options.sum.displayName": "Сумма", "Calculations.Options.sum.label": "Сумма", + "CalendarCard.untitled": "Без названия", + "CardActionsMenu.copiedLink": "Скопировано!", + "CardActionsMenu.copyLink": "Копировать ссылку", + "CardActionsMenu.delete": "Удалить", + "CardActionsMenu.duplicate": "Дублировать", "CardBadges.title-checkboxes": "Флажки", "CardBadges.title-comments": "Комментарии", "CardBadges.title-description": "Эта карточка имеет описание", @@ -68,20 +77,21 @@ "CardDetail.add-icon": "Добавить иконку", "CardDetail.add-property": "+ Добавить свойство", "CardDetail.addCardText": "добавить текст карточки", + "CardDetail.limited-body": "Перейдите на наш тарифный план Professional или Enterprise, чтобы просматривать архивные карточки, иметь неограниченное количество просмотров для каждой доски, неограниченное количество карточек и многое другое.", + "CardDetail.limited-button": "Обновление", + "CardDetail.limited-title": "Эта карточка скрыта", "CardDetail.moveContent": "переместить содержимое карты", "CardDetail.new-comment-placeholder": "Добавить комментарий...", "CardDetailProperty.confirm-delete-heading": "Подтвердить удаление свойства", "CardDetailProperty.confirm-delete-subtext": "Вы действительно хотите удалить свойство \"{propertyName}\"? При его удалении свойство будет удалено со всех карточек на этой доске.", "CardDetailProperty.confirm-property-name-change-subtext": "Вы действительно хотите изменить свойство \"{propertyName}\" {customText}? Это повлияет на значение(-я) на {numOfCards} карточке(-ах) на этой доске и может привести к потере данных.", - "CardDetailProperty.confirm-property-type-change": "Подтвердите изменение типа свойства!", + "CardDetailProperty.confirm-property-type-change": "Подтвердите изменение типа свойства", "CardDetailProperty.delete-action-button": "Удалить", "CardDetailProperty.property-change-action-button": "Изменить свойство", "CardDetailProperty.property-changed": "Свойство изменено успешно!", "CardDetailProperty.property-deleted": "{propertyName} успешно удалено!", "CardDetailProperty.property-name-change-subtext": "тип из \"{oldPropType}\" в \"{newPropType}\"", - "CardDetailProperty.property-type-change-subtext": "название для \"{newPropName}\"", - "CardDialog.copiedLink": "Скопировано!", - "CardDialog.copyLink": "Копировать ссылку", + "CardDetial.limited-link": "Узнайте больше о наших планах.", "CardDialog.delete-confirmation-dialog-button-text": "Удалить", "CardDialog.delete-confirmation-dialog-heading": "Подтвердите удаление карточки!", "CardDialog.editing-template": "Вы редактируете шаблон.", @@ -91,7 +101,8 @@ "Categories.CreateCategoryDialog.Placeholder": "Назовите свою категорию", "Categories.CreateCategoryDialog.UpdateText": "Обновить", "CenterPanel.Login": "Логин", - "CenterPanel.Share": "Поделится", + "CenterPanel.Share": "Поделиться", + "CloudMessage.cloud-server": "Получите свой бесплатный облачный сервер.", "ColorOption.selectColor": "Выберите цвет {color}", "Comment.delete": "Удалить", "CommentsList.send": "Отправить", @@ -118,6 +129,7 @@ "DeleteBoardDialog.confirm-cancel": "Отмена", "DeleteBoardDialog.confirm-delete": "Удалить", "DeleteBoardDialog.confirm-info": "Вы уверены, что хотите удалить доску \"{boardTitle}\"? Ее удаление приведет к удалению всех карточек на доске.", + "DeleteBoardDialog.confirm-info-template": "Вы уверены, что хотите удалить шаблон доски “{boardTitle}”?", "DeleteBoardDialog.confirm-tite": "Подтвердить удаление доски", "DeleteBoardDialog.confirm-tite-template": "Подтвердите удаление шаблона Доски", "Dialog.closeDialog": "Закрыть диалог", @@ -130,23 +142,16 @@ "Filter.not-includes": "не содержит", "FilterComponent.add-filter": "+ Добавить фильтр", "FilterComponent.delete": "Удалить", - "FindBoFindBoardsDialog.IntroText": "Поиск досок", + "FindBoardsDialog.IntroText": "Поиск досок", "FindBoardsDialog.NoResultsFor": "Нет результатов для \"{searchQuery}\"", "FindBoardsDialog.NoResultsSubtext": "Проверьте правильность написания или попробуйте другой запрос.", "FindBoardsDialog.SubTitle": "Введите запрос, чтобы найти доску. Используйте ВВЕРХ/ВНИЗ для просмотра. ENTER для выбора, ESC для закрытия", "FindBoardsDialog.Title": "Найти доски", - "GalleryCard.copiedLink": "Скопировано!", - "GalleryCard.copyLink": "Копировать ссылку", - "GalleryCard.delete": "Удалить", - "GalleryCard.duplicate": "Создать дубликат", "GroupBy.hideEmptyGroups": "Скрыть {count} пустых групп", "GroupBy.showHiddenGroups": "Показать {count} скрытых групп", "GroupBy.ungroup": "Разгруппировать", - "KanbanCard.copiedLink": "Скопировано!", - "KanbanCard.copyLink": "Копировать ссылку", - "KanbanCard.delete": "Удалить", - "KanbanCard.duplicate": "Создать дубликат", "KanbanCard.untitled": "Без названия", + "Mutator.new-board-from-template": "новая доска из шаблона", "Mutator.new-card-from-template": "новая карточка из шаблона", "Mutator.new-template-from-card": "новый шаблон из карточки", "OnboardingTour.AddComments.Body": "Вы можете комментировать проблемы и даже @упоминать своих коллег-пользователей Mattermost, чтобы привлечь их внимание.", @@ -186,18 +191,19 @@ "RegistrationLink.confirmRegenerateToken": "Это сделает недействительными ссылки, которые ранее были общими. Продолжить?", "RegistrationLink.copiedLink": "Скопировано!", "RegistrationLink.copyLink": "Скопировать ссылку", - "RegistrationLink.description": "Расшарить эту ссылку для создания аккаунтов:", + "RegistrationLink.description": "Поделитесь этой ссылкой с другими для создания аккаунтов:", "RegistrationLink.regenerateToken": "Пересоздать токен", "RegistrationLink.tokenRegenerated": "Регистрационная ссылка пересоздана", - "ShareBoard.PublishDescription": "Публикуйте и делитесь ссылкой \"только для чтения\" со всеми пользователями сети", + "ShareBoard.PublishDescription": "Публикуйте и делитесь ссылкой \"только для чтения\" со всеми пользователями сети.", "ShareBoard.PublishTitle": "Опубликовать в Интернете", "ShareBoard.ShareInternal": "Поделиться внутри организации", - "ShareBoard.ShareInternalDescription": "Пользователи, у которых есть разрешения, смогут использовать эту ссылку", + "ShareBoard.ShareInternalDescription": "Пользователи, у которых есть разрешения, смогут использовать эту ссылку.", "ShareBoard.Title": "Поделится Доской", "ShareBoard.confirmRegenerateToken": "Это сделает недействительными ссылки, которые ранее были общими. Продолжить?", "ShareBoard.copiedLink": "Скопировано!", "ShareBoard.copyLink": "Скопировать ссылку", "ShareBoard.regenerate": "Восстановить токен", + "ShareBoard.searchPlaceholder": "Поиск людей", "ShareBoard.teamPermissionsText": "Все в команде {teamName}", "ShareBoard.tokenRegenrated": "Токен пересоздан", "ShareBoard.userPermissionsRemoveMemberText": "Удалить участника", @@ -238,6 +244,7 @@ "TableHeaderMenu.insert-right": "Вставить справа", "TableHeaderMenu.sort-ascending": "Сортировать по возрастанию", "TableHeaderMenu.sort-descending": "Сортировать по убыванию", + "TableRow.delete": "Удалить", "TableRow.open": "Открыть", "TopBar.give-feedback": "Дать обратную связь", "URLProperty.copiedLink": "Скопировано!", @@ -255,6 +262,7 @@ "View.NewCalendarTitle": "Просмотр календаря", "View.NewGalleryTitle": "Представление \"галерея\"", "View.NewTableTitle": "Вид таблицы", + "View.NewTemplateTitle": "Шаблон без названия", "View.Table": "Таблица", "ViewHeader.add-template": "Новый шаблон", "ViewHeader.delete-template": "Удалить", @@ -270,7 +278,6 @@ "ViewHeader.new": "Создать", "ViewHeader.properties": "Свойства", "ViewHeader.properties-menu": "Меню свойств", - "ViewHeader.search": "Поиск", "ViewHeader.search-text": "Поиск текста", "ViewHeader.select-a-template": "Выбрать шаблон", "ViewHeader.set-default-template": "Установить по умолчанию", @@ -278,34 +285,77 @@ "ViewHeader.untitled": "Без названия", "ViewHeader.view-header-menu": "Посмотреть меню заголовка", "ViewHeader.view-menu": "Посмотреть меню", + "ViewLimitDialog.Heading": "Достигнут лимит просмотров на доске", + "ViewLimitDialog.PrimaryButton.Title.Admin": "Обновление", + "ViewLimitDialog.PrimaryButton.Title.RegularUser": "Сообщить администратору", + "ViewLimitDialog.Subtext.Admin": "Перейдите на наш план Professional или Enterprise, чтобы иметь неограниченное количество просмотров на досках, неограниченное количество карточек и многое другое.", + "ViewLimitDialog.Subtext.Admin.PricingPageLink": "Узнайте больше о наших тарифах.", + "ViewLimitDialog.Subtext.RegularUser": "Сообщите своему администратору, чтобы перейти на наш план Professional или Enterprise, чтобы иметь неограниченные количество просмотров на доске, карточек и многое другое.", + "ViewLimitDialog.UpgradeImg.AltText": "обновить изображение", + "ViewLimitDialog.notifyAdmin.Success": "Ваш администратор был уведомлен", "ViewTitle.hide-description": "скрыть описание", "ViewTitle.pick-icon": "Выбрать иконку", "ViewTitle.random-icon": "Случайным образом", "ViewTitle.remove-icon": "Убрать иконку", "ViewTitle.show-description": "Показать описание", "ViewTitle.untitled-board": "Доска Без Названия", - "WelcomePage.Description": "Доски — это инструмент управления проектами, который помогает определять, организовывать, отслеживать и управлять работой между командами, используя знакомое представление доски Kanban", + "WelcomePage.Description": "Доски — это инструмент управления проектами, который помогает определять, организовывать, отслеживать и управлять работой между командами, используя знакомое представление доски Kanban.", "WelcomePage.Explore.Button": "Исследовать", "WelcomePage.Heading": "Добро пожаловать на Доски", "WelcomePage.NoThanks.Text": "Нет спасибо, сам разберусь", "Workspace.editing-board-template": "Вы редактируете шаблон доски.", + "boardSelector.confirm-link-board": "Привязать доску к каналу", + "boardSelector.confirm-link-board-button": "Да, ссылка доски", + "boardSelector.confirm-link-board-subtext": "Связывание доски \"{boardName}\" с этим каналом даст всем участникам этого канала доступ на редактирование доски. Вы уверены, что хотите связать это?", + "boardSelector.create-a-board": "Создать доску", + "boardSelector.link": "Ссылка", + "boardSelector.search-for-boards": "Поиск досок", + "boardSelector.title": "Ссылки на доски", + "boardSelector.unlink": "Отключить", "calendar.month": "Месяц", "calendar.today": "СЕГОДНЯ", "calendar.week": "Неделя", + "cloudMessage.learn-more": "Учить больше", "createImageBlock.failed": "Не удалось загрузить файл. Достигнут предел размера файла.", "default-properties.badges": "Комментарии и описание", "default-properties.title": "Заголовок", + "error.back-to-home": "Вернуться на Главную", + "error.back-to-team": "Вернуться в команду", + "error.board-not-found": "Доска не найдена.", + "error.go-login": "Логин", + "error.invalid-read-only-board": "У Вас нет доступа к этой доске. Войдите, чтобы получить доступ к Доскам.", + "error.not-logged-in": "Возможно, срок действия Вашего сеанса истек или Вы не вошли в систему. Войдите еще раз, чтобы получить доступ к Доскам.", "error.page.title": "Извините, что-то пошло не так", + "error.team-undefined": "Не корректная команда.", + "error.unknown": "Произошла ошибка.", "generic.previous": "Предыдущий", "imagePaste.upload-failed": "Некоторые файлы не загружены. Достигнут предел размера файла", + "limitedCard.title": "Карточки скрыты", "login.log-in-button": "Вход в систему", "login.log-in-title": "Вход в систему", "login.register-button": "или создать аккаунт, если у Вас его нет", + "notification-box-card-limit-reached.close-tooltip": "Отложить на 10 дней", + "notification-box-card-limit-reached.contact-link": "уведомить Вашего администратора", + "notification-box-card-limit-reached.link": "Перейти на платный тариф", + "notification-box-card-limit-reached.title": "{cards} карты, скрытые на доске", + "notification-box-cards-hidden.title": "Это действие скрыло другую карточку", + "notification-box.card-limit-reached.not-admin.text": "Чтобы получить доступ к архивным карточкам, Вы можете {contactLink} перейти на платный тариф.", + "notification-box.card-limit-reached.text": "Достигнут лимит карточки, чтобы просмотреть старые карточки, {link}", "register.login-button": "или войти в систему, если у вас уже есть аккаунт", "register.signup-title": "Зарегистрируйте свой аккаунт", + "rhs-boards.add": "Добавить", + "rhs-boards.last-update-at": "Последнее обновление: {datetime}", + "rhs-boards.link-boards-to-channel": "Связать доски с {channelName}", + "rhs-boards.linked-boards": "Связанные доски", + "rhs-boards.no-boards-linked-to-channel": "К каналу {channelName} пока не подключены доски.", + "rhs-boards.no-boards-linked-to-channel-description": "Доски — это инструмент управления проектами, который помогает определять, организовывать, отслеживать и управлять работой между командами, используя знакомое представление доски Канбан.", + "rhs-boards.unlink-board": "Отвязать доску", + "rhs-channel-boards-header.title": "Доски", "share-board.publish": "Опубликовать", "share-board.share": "Поделиться", + "shareBoard.channels-select-group": "Каналы", "shareBoard.lastAdmin": "Доски должны иметь хотя бы одного администратора", + "shareBoard.members-select-group": "Участники", "tutorial_tip.finish_tour": "Готово", "tutorial_tip.got_it": "Понятно", "tutorial_tip.ok": "Следующий", diff --git a/webapp/i18n/sk.json b/webapp/i18n/sk.json index 636b87bc0..48f3d36c9 100644 --- a/webapp/i18n/sk.json +++ b/webapp/i18n/sk.json @@ -62,9 +62,6 @@ "CardDetailProperty.property-changed": "Zmena vlastnosti úspešná!", "CardDetailProperty.property-deleted": "Mazanie {propertyName} úspešné!", "CardDetailProperty.property-name-change-subtext": "typ od \"{oldPropType}\" do \"{newPropType}\"", - "CardDetailProperty.property-type-change-subtext": "meno po \"{newPropName}\"", - "CardDialog.copiedLink": "Skopírované!", - "CardDialog.copyLink": "Kopíruj odkaz", "CardDialog.editing-template": "Editujete template.", "CardDialog.nocard": "Karta neexistuje alebo je neprístupná.", "ColorOption.selectColor": "Vyber {color} farbu", @@ -100,15 +97,7 @@ "Filter.not-includes": "nezahŕňa", "FilterComponent.add-filter": "+ Pridaj filter", "FilterComponent.delete": "Odstrániť", - "GalleryCard.copiedLink": "Skopírované!", - "GalleryCard.copyLink": "Skopírovať odkaz", - "GalleryCard.delete": "Odstrániť", - "GalleryCard.duplicate": "Duplikuj", "GroupBy.ungroup": "Zrušiť zoskupenie", - "KanbanCard.copiedLink": "Skopírované!", - "KanbanCard.copyLink": "Skopírovať odkaz", - "KanbanCard.delete": "Odstrániť", - "KanbanCard.duplicate": "Duplikuj", "KanbanCard.untitled": "Nepomenované", "Mutator.new-card-from-template": "nová karta z template-u", "Mutator.new-template-from-card": "nový template z karty", @@ -193,7 +182,6 @@ "ViewHeader.group-by": "Zoskupiť podľa: {property}", "ViewHeader.new": "Nový", "ViewHeader.properties": "Vlastnosti", - "ViewHeader.search": "Hľadať", "ViewHeader.search-text": "Hľadať text", "ViewHeader.select-a-template": "vyber template", "ViewHeader.set-default-template": "Nastaviť ako predvolenú", diff --git a/webapp/i18n/sv.json b/webapp/i18n/sv.json index 4dd11c77a..18f886d35 100644 --- a/webapp/i18n/sv.json +++ b/webapp/i18n/sv.json @@ -8,9 +8,11 @@ "BoardComponent.no-property-title": "Objekt med en tom {property} egenskap grupperas här. Denna kolumn kan inte tas bort.", "BoardComponent.show": "Visa", "BoardMember.schemeAdmin": "Administratör", + "BoardMember.schemeCommenter": "Kommentator", "BoardMember.schemeEditor": "Redaktör", "BoardMember.schemeNone": "Inget", "BoardMember.schemeViewer": "Granskare", + "BoardMember.schemeViwer": "Åskådare", "BoardPage.newVersion": "En ny version av Boards finns tillgänglig. Klicka här för att uppdatera.", "BoardPage.syncFailed": "Denna Board kan ha blivit raderad eller så har din behörighet tagits bort.", "BoardTemplateSelector.add-template": "Ny mall", @@ -20,9 +22,10 @@ "BoardTemplateSelector.edit-template": "Ändra", "BoardTemplateSelector.plugin.no-content-description": "Lägg till en Board till sidofältet genom att använda en av mallarna nedan eller starta med en tom.{lineBreak} Medlemmar i \"{teamName}\" kommer ha åtkomst till Board som skapas här.", "BoardTemplateSelector.plugin.no-content-title": "Skapa en Board i {teamName}", - "BoardTemplateSelector.title": "Skapa en Board", + "BoardTemplateSelector.title": "Skapa en board", "BoardTemplateSelector.use-this-template": "Använd den här mallen", "BoardsSwitcher.Title": "Hitta Board", + "BoardsUnfurl.Limited": "Ytterligare uppgifter är dolda eftersom kortet är arkiverat", "BoardsUnfurl.Remainder": "+{remainder} mer", "BoardsUnfurl.Updated": "Uppdaterad {time}", "Calculations.Options.average.displayName": "Genomsnitt", @@ -68,20 +71,21 @@ "CardDetail.add-icon": "Lägg till ikon", "CardDetail.add-property": "+ Lägg till egenskap", "CardDetail.addCardText": "lägg till korttext", - "CardDetail.moveContent": "flytta kortinnehåll", + "CardDetail.limited-body": "Uppgradera till Professional- eller Enterprise-abonnemang för att visa arkiverade kort, få obegränsat antal visningar per board, obegränsat antal kort och mycket mer.", + "CardDetail.limited-button": "Uppgradera", + "CardDetail.limited-title": "Detta kort är dolt", + "CardDetail.moveContent": "Flytta kortinnehåll", "CardDetail.new-comment-placeholder": "Lägg till kommentar...", - "CardDetailProperty.confirm-delete-heading": "Bekräfta att ta bort egenskap", + "CardDetailProperty.confirm-delete-heading": "Bekräfta ta bort egenskap", "CardDetailProperty.confirm-delete-subtext": "Är du säker på att du vill ta bort egenskapen \"{propertyName}\"? Om du raderar den kommer egenskapen tas bort från alla kort på tavlan.", "CardDetailProperty.confirm-property-name-change-subtext": "Är du säker du vill ändra egenskapen \"{propertyName}\" {customText}? Detta kommer påverka alla värden på {numOfCards} kort på den här boarden och kan innebära att du förlorar information.", - "CardDetailProperty.confirm-property-type-change": "Bekräfta ändring av egenskapstyp!", + "CardDetailProperty.confirm-property-type-change": "Bekräfta ändring av egenskapstyp", "CardDetailProperty.delete-action-button": "Ta bort", "CardDetailProperty.property-change-action-button": "Ändra egenskap", "CardDetailProperty.property-changed": "Ändrade egenskap!", "CardDetailProperty.property-deleted": "{propertyName} har raderats!", "CardDetailProperty.property-name-change-subtext": "typ från \"{oldPropType}\" till \"{newPropType}\"", - "CardDetailProperty.property-type-change-subtext": "namn till \"{newPropName}\"", - "CardDialog.copiedLink": "Kopierad!", - "CardDialog.copyLink": "Kopiera länk", + "CardDetial.limited-link": "Läs mer om våra abonnemang.", "CardDialog.delete-confirmation-dialog-button-text": "Radera", "CardDialog.delete-confirmation-dialog-heading": "Bekräfta att kortet ska raderas!", "CardDialog.editing-template": "Du redigerar en mall.", @@ -92,6 +96,7 @@ "Categories.CreateCategoryDialog.UpdateText": "Uppdatera", "CenterPanel.Login": "Logga in", "CenterPanel.Share": "Dela", + "CloudMessage.cloud-server": "Skaffa din egen molnserver gratis.", "ColorOption.selectColor": "Välj {color} färg", "Comment.delete": "Radera", "CommentsList.send": "Skicka", @@ -118,8 +123,9 @@ "DeleteBoardDialog.confirm-cancel": "Avbryt", "DeleteBoardDialog.confirm-delete": "Radera", "DeleteBoardDialog.confirm-info": "Är du säker på att du vill ta bort board “{boardTitle}”? När du tar bort den kommer du radera alla kort på board.", + "DeleteBoardDialog.confirm-info-template": "Är du säker på att du vill ta bort board-mallen \"{boardTitle}\"?", "DeleteBoardDialog.confirm-tite": "Bekräfta att ta bort board", - "DeleteBoardDialog.confirm-tite-template": "Bekräfta att Board-mallen ska raderas", + "DeleteBoardDialog.confirm-tite-template": "Bekräfta att board-mallen ska raderas", "Dialog.closeDialog": "Stäng dialog", "EditableDayPicker.today": "Idag", "Error.mobileweb": "Webbåtkomst via mobilen är i tidig betaversion. All funktionalitet kanske inte är tillgänglig.", @@ -130,23 +136,15 @@ "Filter.not-includes": "inkluderar inte", "FilterComponent.add-filter": "+ Lägg till filter", "FilterComponent.delete": "Radera", - "FindBoFindBoardsDialog.IntroText": "Sök efter boards", "FindBoardsDialog.NoResultsFor": "Inga sökresultat för \"{searchQuery}\"", "FindBoardsDialog.NoResultsSubtext": "Kontrollera stavningen eller sök igen.", "FindBoardsDialog.SubTitle": "Skriv för att hitta en board. Använd UPP/NER för att bläddra. RETUR för att välja, ESC för att avbryta", "FindBoardsDialog.Title": "Hitta board", - "GalleryCard.copiedLink": "Kopierad!", - "GalleryCard.copyLink": "Kopiera länk", - "GalleryCard.delete": "Radera", - "GalleryCard.duplicate": "Duplicera", "GroupBy.hideEmptyGroups": "Dölj {count} tomma grupper", "GroupBy.showHiddenGroups": "Visa {count} dolda grupper", "GroupBy.ungroup": "Dela upp grupp", - "KanbanCard.copiedLink": "Kopierad!", - "KanbanCard.copyLink": "Kopiera länk", - "KanbanCard.delete": "Radera", - "KanbanCard.duplicate": "Kopiera", "KanbanCard.untitled": "Saknar titel", + "Mutator.new-board-from-template": "ny board från en mall", "Mutator.new-card-from-template": "nytt kort från mall", "Mutator.new-template-from-card": "ny mall från kort", "OnboardingTour.AddComments.Body": "Du kan kommentera ämnen och till och med @omnämna andra Mattermost-användare för att få deras uppmärksamhet.", @@ -173,7 +171,7 @@ "PropertyType.Date": "Datum", "PropertyType.Email": "Email", "PropertyType.File": "Fil eller media", - "PropertyType.MultiSelect": "Alternativ (flervals)", + "PropertyType.MultiSelect": "Flervalsalternativ", "PropertyType.Number": "Tal", "PropertyType.Person": "Person", "PropertyType.Phone": "Telefon", @@ -189,10 +187,10 @@ "RegistrationLink.description": "Dela denna länk med andra för att skapa konton:", "RegistrationLink.regenerateToken": "Återskapa åtkomstnyckel", "RegistrationLink.tokenRegenerated": "Registreringslänk återskapad", - "ShareBoard.PublishDescription": "Publicera och dela en \"skrivskyddad\" länk till alla på webben", + "ShareBoard.PublishDescription": "Publicera och dela en skrivskyddad länk med alla på webben.", "ShareBoard.PublishTitle": "Publicera på webben", "ShareBoard.ShareInternal": "Dela internt", - "ShareBoard.ShareInternalDescription": "Användare som har behörighet kan använda denna länk", + "ShareBoard.ShareInternalDescription": "Användare som har behörighet kan använda denna länk.", "ShareBoard.Title": "Dela Board", "ShareBoard.confirmRegenerateToken": "Det här kommer att göra tidigare delade länkar ogiltiga. Vill du fortsätta?", "ShareBoard.copiedLink": "Kopierad!", @@ -240,7 +238,7 @@ "TableHeaderMenu.sort-descending": "Sortera fallande", "TableRow.delete": "Ta bort", "TableRow.open": "Öppna", - "TopBar.give-feedback": "Ge feedback", + "TopBar.give-feedback": "Ge återkoppling", "URLProperty.copiedLink": "Kopierad!", "URLProperty.copy": "Kopiera", "URLProperty.edit": "Ändra", @@ -256,6 +254,7 @@ "View.NewCalendarTitle": "Kalendervy", "View.NewGalleryTitle": "Galleri vy", "View.NewTableTitle": "Tabellvy", + "View.NewTemplateTitle": "Namnlös mall", "View.Table": "Tabell", "ViewHeader.add-template": "Ny mall", "ViewHeader.delete-template": "Radera", @@ -271,7 +270,6 @@ "ViewHeader.new": "Ny", "ViewHeader.properties": "Egenskaper", "ViewHeader.properties-menu": "Menyn Egenskaper", - "ViewHeader.search": "Sök", "ViewHeader.search-text": "Sök efter kort", "ViewHeader.select-a-template": "Välj en mall", "ViewHeader.set-default-template": "Sätt som förvald", @@ -279,29 +277,52 @@ "ViewHeader.untitled": "Saknar titel", "ViewHeader.view-header-menu": "Visa huvudmenyn", "ViewHeader.view-menu": "Visa menyn", + "ViewLimitDialog.Heading": "Gränsen för antalet visningar per board har uppnåtts", + "ViewLimitDialog.PrimaryButton.Title.Admin": "Uppgradera", + "ViewLimitDialog.PrimaryButton.Title.RegularUser": "Meddela administratör", + "ViewLimitDialog.Subtext.Admin": "Uppgradera till Professional- eller Enterprise-abonnemang för att få obegränsat antal visningar per board, obegränsat antal kort och mycket mer.", + "ViewLimitDialog.Subtext.Admin.PricingPageLink": "Läs mer om våra abonnemang.", + "ViewLimitDialog.Subtext.RegularUser": "Meddela din administratör att uppgradera till Professional- eller Enterprise-abonnemang för att få obegränsat antal visningar per board, obegränsat antal kort och mycket mer.", + "ViewLimitDialog.UpgradeImg.AltText": "bild som föreställer en uppgradering", "ViewTitle.hide-description": "dölj beskrivning", "ViewTitle.pick-icon": "Välj ikon", "ViewTitle.random-icon": "Slumpmässig", "ViewTitle.remove-icon": "Ta bort ikon", "ViewTitle.show-description": "visa beskrivning", "ViewTitle.untitled-board": "Board utan titel", - "WelcomePage.Description": "Anslagstavlan är ett projekthanteringsverktyg som hjälper till att definiera, organisera, spåra och hantera arbete mellan team med hjälp av en välbekant Kanban-vy", + "WelcomePage.Description": "Boards är ett projekthanteringsverktyg som hjälper till att definiera, organisera, spåra och hantera arbete mellan team med hjälp av en välbekant Kanban-vy.", "WelcomePage.Explore.Button": "Starta en rundtur", - "WelcomePage.Heading": "Välkommen till Anslagstavlan", + "WelcomePage.Heading": "Välkommen till Boards", "WelcomePage.NoThanks.Text": "Nej tack, jag löser det själv", "Workspace.editing-board-template": "Du redigerar en tavelmall.", "calendar.month": "Månad", "calendar.today": "IDAG", "calendar.week": "Vecka", + "cloudMessage.learn-more": "Läs mer", "createImageBlock.failed": "Det går inte att ladda upp filen. Gränsen för filstorlek har uppnåtts.", "default-properties.badges": "Kommentarer och beskrivning", "default-properties.title": "Titel", + "error.back-to-home": "Tillbaka till förstasidan", + "error.back-to-team": "Tillbaka till team", + "error.board-not-found": "Board finns inte.", + "error.go-login": "Logga in", + "error.invalid-read-only-board": "Du har inte tillgång till denna board. Logga in för att få tillgång till Boards.", + "error.not-logged-in": "Din session kan ha löpt ut eller så är du inte inloggad. Logga in igen för att få tillgång till Boards.", "error.page.title": "Oops, något gick fel", + "error.team-undefined": "Inte ett giltigt team.", + "error.unknown": "Ett fel inträffade.", "generic.previous": "Föregående", "imagePaste.upload-failed": "Vissa filer har inte laddats upp. Gränsen för filstorlek har nåtts", + "limitedCard.title": "Korten doldes", "login.log-in-button": "Logga in", "login.log-in-title": "Logga in", "login.register-button": "eller skapa ett konto om du inte redan har ett", + "notification-box-card-limit-reached.close-tooltip": "Sov i 10 dagar", + "notification-box-card-limit-reached.link": "uppgradera till ett betal-abonnemang", + "notification-box-card-limit-reached.title": "{cards} kort dolda från board", + "notification-box-cards-hidden.title": "Din åtgärd dolde ett annat kort", + "notification-box.card-limit-reached.not-admin.text": "Om du vill komma åt arkiverade kort kontaktar du din administratör för att uppgradera till ett betal-abonnemang.", + "notification-box.card-limit-reached.text": "Gränsen för kort har nåtts, för att visa äldre kort, {link}", "register.login-button": "eller logga in om du redan har ett konto", "register.signup-title": "Registrera dig för ett konto", "share-board.publish": "Publicera", diff --git a/webapp/i18n/tr.json b/webapp/i18n/tr.json index 5220de31f..1c6e383db 100644 --- a/webapp/i18n/tr.json +++ b/webapp/i18n/tr.json @@ -8,21 +8,25 @@ "BoardComponent.no-property-title": "{property} alanı boş olan ögeler buraya atanır. Bu sütun silinemez.", "BoardComponent.show": "Görüntüle", "BoardMember.schemeAdmin": "Yönetici", + "BoardMember.schemeCommenter": "Yorumcu", "BoardMember.schemeEditor": "Düzenleyici", "BoardMember.schemeNone": "Yok", "BoardMember.schemeViewer": "Görüntüleyici", + "BoardMember.schemeViwer": "İzleyici", + "BoardMember.unlinkChannel": "Bağlantıyı kaldır", "BoardPage.newVersion": "Yeni bir pano sürümü yayınlanmış. Yeniden yüklemek için buraya tıklayın.", "BoardPage.syncFailed": "Pano silinmiş ya da erişim izni geri alınmış olabilir.", "BoardTemplateSelector.add-template": "Yeni kalıp", - "BoardTemplateSelector.create-empty-board": "Boş pano oluştur", + "BoardTemplateSelector.create-empty-board": "Boş pano ekle", "BoardTemplateSelector.delete-template": "Sil", - "BoardTemplateSelector.description": "Başlamanıza yardımcı olacak bir kalıp seçin. Kalıbı gereksinimlerinize göre kolayca özelleştirin ya da sıfırdan başlamak için boş bir pano oluşturun.", + "BoardTemplateSelector.description": "Kalıplardan birini kullanarak ya da sıfırdan başlayarak yan çubuğa bir pano ekleyin.", "BoardTemplateSelector.edit-template": "Düzenle", - "BoardTemplateSelector.plugin.no-content-description": "Aşağıda tanımlanan kalıplardan herhangi birini kullanarak yan çubuğa bir pano ekleyin ya da sıfırdan başlayın.{lineBreak} \"{teamName}\" üyeleri burada oluşturulan panolara erişebilecek.", - "BoardTemplateSelector.plugin.no-content-title": "{teamName} çalışma alanında bir pano oluştur", - "BoardTemplateSelector.title": "Bir pano oluştur", + "BoardTemplateSelector.plugin.no-content-description": "Aşağıdaki kalıplardan birini kullanarak ya da sıfırdan başlayarak yan çubuğa bir pano ekleyin.", + "BoardTemplateSelector.plugin.no-content-title": "Bir pano ekleyin", + "BoardTemplateSelector.title": "Bir pano ekle", "BoardTemplateSelector.use-this-template": "Bu kalıp kullanılsın", "BoardsSwitcher.Title": "Pano arama", + "BoardsUnfurl.Limited": "Kart arşivlendiğinden ek bilgiler gizleniyor", "BoardsUnfurl.Remainder": "+{remainder} diğer", "BoardsUnfurl.Updated": "Güncellenme: {time}", "Calculations.Options.average.displayName": "Ortalama", @@ -59,29 +63,35 @@ "Calculations.Options.range.label": "Aralık", "Calculations.Options.sum.displayName": "Toplam", "Calculations.Options.sum.label": "Toplam", + "CalendarCard.untitled": "Başlıksız", + "CardActionsMenu.copiedLink": "Kopyalandı!", + "CardActionsMenu.copyLink": "Bağlantıyı kopyala", + "CardActionsMenu.delete": "Sil", + "CardActionsMenu.duplicate": "Kopyala", "CardBadges.title-checkboxes": "İşaret kutuları", "CardBadges.title-comments": "Yorumlar", "CardBadges.title-description": "Bu kartın bir açıklaması var", - "CardDetail.Follow": "Takip et", - "CardDetail.Following": "Takip ediliyor", + "CardDetail.Follow": "İzle", + "CardDetail.Following": "İzleniyor", "CardDetail.add-content": "İçerik ekle", "CardDetail.add-icon": "Simge ekle", "CardDetail.add-property": "+ Alan ekle", "CardDetail.addCardText": "kart metni ekle", - "CardDetail.moveContent": "kart içeriğini taşı", + "CardDetail.limited-body": "Arşivlenmiş kartları görüntülemek, her pano için sınırsız görüntüleme, sınırsız sayıda kart gibi daha fazla özelliğe sahip olmak için Professional ya da Enterprise tarifesine yükseltin.", + "CardDetail.limited-button": "Yükselt", + "CardDetail.limited-title": "Bu kart gizli", + "CardDetail.moveContent": "Kart içeriğini taşı", "CardDetail.new-comment-placeholder": "Bir yorum ekle...", "CardDetailProperty.confirm-delete-heading": "Özelliği silmeyi onaylayın", "CardDetailProperty.confirm-delete-subtext": "\"{propertyName}\" özelliğini silmek istediğinize emin misiniz? Bu işlem özelliği panodaki tüm kartlardan siler.", "CardDetailProperty.confirm-property-name-change-subtext": "\"{propertyName}\" {customText} özelliğini değiştirmek istediğinize emin misiniz? Bu işlem bu panodaki {numOfCards} kartı etkiler ve veri kaybına yol açabilir.", - "CardDetailProperty.confirm-property-type-change": "Özellik türü değişimini onaylayın!", + "CardDetailProperty.confirm-property-type-change": "Özellik türü değişimini onaylayın", "CardDetailProperty.delete-action-button": "Sil", "CardDetailProperty.property-change-action-button": "Özelliği değiştir", "CardDetailProperty.property-changed": "Özellik değiştirildi!", "CardDetailProperty.property-deleted": "{propertyName} silindi!", "CardDetailProperty.property-name-change-subtext": "\"{oldPropType}\" türünden \"{newPropType}\" türüne", - "CardDetailProperty.property-type-change-subtext": "\"{newPropName}\" adına", - "CardDialog.copiedLink": "Kopyalandı!", - "CardDialog.copyLink": "Bağlantıyı kopyala", + "CardDetial.limited-link": "Tarifelerimiz hakkında ayrıntılı bilgi alın.", "CardDialog.delete-confirmation-dialog-button-text": "Sil", "CardDialog.delete-confirmation-dialog-heading": "Kartı silmeyi onaylayın!", "CardDialog.editing-template": "Bir kalıbı düzenliyorsunuz.", @@ -92,6 +102,7 @@ "Categories.CreateCategoryDialog.UpdateText": "Güncelle", "CenterPanel.Login": "Oturum aç", "CenterPanel.Share": "Paylaş", + "CloudMessage.cloud-server": "Kendi ücretsiz bulut sunucunuzu edinin.", "ColorOption.selectColor": "{color} rengi seçin", "Comment.delete": "Sil", "CommentsList.send": "Gönder", @@ -118,8 +129,9 @@ "DeleteBoardDialog.confirm-cancel": "İptal", "DeleteBoardDialog.confirm-delete": "Sil", "DeleteBoardDialog.confirm-info": "“{boardTitle}” panosunu silmek istediğinize emin misiniz? Silme işlemi bu panodaki tüm kartları siler.", + "DeleteBoardDialog.confirm-info-template": "“{boardTitle}” pano kalıbını silmek istediğinize emin misiniz?", "DeleteBoardDialog.confirm-tite": "Pano silmeyi onayla", - "DeleteBoardDialog.confirm-tite-template": "Kart kalıbını silmeyi onayla", + "DeleteBoardDialog.confirm-tite-template": "Pano kalıbını silmeyi onayla", "Dialog.closeDialog": "Pencereyi kapat", "EditableDayPicker.today": "Bugün", "Error.mobileweb": "Mobil web desteği şu anda erken beta aşamasındadır. Tüm işlevler kullanılamıyor olabilir.", @@ -130,23 +142,17 @@ "Filter.not-includes": "şunu içermeyen", "FilterComponent.add-filter": "+ Süzgeç ekle", "FilterComponent.delete": "Sil", - "FindBoFindBoardsDialog.IntroText": "Pano arama", + "FindBoardsDialog.IntroText": "Pano arama", "FindBoardsDialog.NoResultsFor": "\"{searchQuery}\" için bir sonuç bulunamadı", "FindBoardsDialog.NoResultsSubtext": "Yazımı denetleyin ya da başka bir arama yapmayı deneyin.", "FindBoardsDialog.SubTitle": "Bulmak istediğiniz pano adını yazmaya başlayın. Gezinmek için YUKAR/AŞAĞI, seçmek için ENTER, vazgeçmek için ESC tuşlarını kullanın", "FindBoardsDialog.Title": "Pano arama", - "GalleryCard.copiedLink": "Kopyalandı!", - "GalleryCard.copyLink": "Bağlantıyı kopyala", - "GalleryCard.delete": "Sil", - "GalleryCard.duplicate": "Kopyala", "GroupBy.hideEmptyGroups": "{count} boş grubu gizle", "GroupBy.showHiddenGroups": "{count} gizli grubu görüntüle", "GroupBy.ungroup": "Gruplamayı kaldır", - "KanbanCard.copiedLink": "Kopyalandı!", - "KanbanCard.copyLink": "Bağlantıyı kopyala", - "KanbanCard.delete": "Sil", - "KanbanCard.duplicate": "Kopyala", + "HideBoard.MenuOption": "Panoyu gizle", "KanbanCard.untitled": "Başlıksız", + "Mutator.new-board-from-template": "kalıptan yeni pano", "Mutator.new-card-from-template": "kalıptan yeni kart oluştur", "Mutator.new-template-from-card": "karttan yeni kalıp oluştur", "OnboardingTour.AddComments.Body": "Sorunlar hakkında yorum yapabilir ve Mattermost kullanıcılarının dikkatini çekmek için @anabilirsiniz.", @@ -172,7 +178,7 @@ "PropertyType.CreatedTime": "Oluşturulma zamanı", "PropertyType.Date": "Tarih", "PropertyType.Email": "E-posta", - "PropertyType.File": "Dosya veya ortam", + "PropertyType.File": "Dosya ya da ortam", "PropertyType.MultiSelect": "Çoklu seçim", "PropertyType.Number": "Sayı", "PropertyType.Person": "Kişi", @@ -189,15 +195,16 @@ "RegistrationLink.description": "Başkalarının hesap ekleyebilmesi için bu bağlantıyı paylaş:", "RegistrationLink.regenerateToken": "Kodu yeniden oluştur", "RegistrationLink.tokenRegenerated": "Kayıt bağlantısı yeniden oluşturuldu", - "ShareBoard.PublishDescription": "Web üzerinde herkese açık olarak \"salt okunur\" bir bağlantı yayınlayın ve paylaşın", + "ShareBoard.PublishDescription": "Web üzerinde herkese açık olarak \"salt okunur\" bir bağlantı yayınlayın ve paylaşın.", "ShareBoard.PublishTitle": "Web üzerinde yayınla", "ShareBoard.ShareInternal": "İçeride paylaş", - "ShareBoard.ShareInternalDescription": "İzni olan kullanıcılar bu bağlantıyı kullanabilecek", + "ShareBoard.ShareInternalDescription": "İzni olan kullanıcılar bu bağlantıyı kullanabilecek.", "ShareBoard.Title": "Panoyu paylaş", "ShareBoard.confirmRegenerateToken": "Bu işlem daha önce paylaşılmış bağlantıları geçersiz kılacak. Devam etmek istiyor musunuz?", "ShareBoard.copiedLink": "Kopyalandı!", "ShareBoard.copyLink": "Bağlantıyı kopyala", "ShareBoard.regenerate": "Kodu yeniden oluştur", + "ShareBoard.searchPlaceholder": "Kişi ve kanal arama", "ShareBoard.teamPermissionsText": "{teamName} takımındaki herkes", "ShareBoard.tokenRegenrated": "Kod yeniden oluşturuldu", "ShareBoard.userPermissionsRemoveMemberText": "Üyelikten çıkar", @@ -244,9 +251,16 @@ "URLProperty.copiedLink": "Kopyalandı!", "URLProperty.copy": "Kopyala", "URLProperty.edit": "Düzenle", + "UndoRedoHotKeys.canRedo": "Yinele", + "UndoRedoHotKeys.canRedo-with-description": "{description} yinele", + "UndoRedoHotKeys.canUndo": "Geri al", + "UndoRedoHotKeys.canUndo-with-description": "{description} geri al", + "UndoRedoHotKeys.cannotRedo": "Yinelenecek bir işlem yok", + "UndoRedoHotKeys.cannotUndo": "Geri alınacak bir işlem yok", "ValueSelector.noOptions": "Herhangi bir seçenek yok. İlk seçeneği eklemek için yazmaya başlayın!", "ValueSelector.valueSelector": "Değer seçici", "ValueSelectorLabel.openMenu": "Menüyü aç", + "VersionMessage.help": "Bu sürümdeki yeniliklere bakın.", "View.AddView": "Görünüm ekle", "View.Board": "Pano", "View.DeleteView": "Görünümü sil", @@ -256,6 +270,7 @@ "View.NewCalendarTitle": "Takvim görünümü", "View.NewGalleryTitle": "Galeri görünümü", "View.NewTableTitle": "Tablo görünümü", + "View.NewTemplateTitle": "Başlıksız kalıp", "View.Table": "Tablo", "ViewHeader.add-template": "Yeni kalıp", "ViewHeader.delete-template": "Sil", @@ -271,7 +286,6 @@ "ViewHeader.new": "Yeni", "ViewHeader.properties": "Özellikler", "ViewHeader.properties-menu": "Özellikler menüsü", - "ViewHeader.search": "Arama", "ViewHeader.search-text": "Kart arama", "ViewHeader.select-a-template": "Bir kalıp seçin", "ViewHeader.set-default-template": "Varsayılan olarak ata", @@ -279,34 +293,91 @@ "ViewHeader.untitled": "Başlıksız", "ViewHeader.view-header-menu": "Başlık menüsünü görüntüle", "ViewHeader.view-menu": "Menüyü görüntüle", + "ViewLimitDialog.Heading": "Bir panoyu görüntüleme sınırına ulaşıldı", + "ViewLimitDialog.PrimaryButton.Title.Admin": "Yükselt", + "ViewLimitDialog.PrimaryButton.Title.RegularUser": "Yöneticiyi bilgilendir", + "ViewLimitDialog.Subtext.Admin": "Panoları sınırsız sayıda görüntüleyebilmek, sınırsız sayıda kart kullanmak ve diğer özellikler için Professional ya da Enterprise tarifemize yükseltin.", + "ViewLimitDialog.Subtext.Admin.PricingPageLink": "Tarifelerimiz hakkında ayrıntılı bilgi alın.", + "ViewLimitDialog.Subtext.RegularUser": "Panoları sınırsız sayıda görüntüleyebilmek, sınırsız sayıda kart kullanabilmek ve diğer özellikler için yöneticinizi Professional ya da Enterprise tarifesine yükseltmesi için bilgilendirin.", + "ViewLimitDialog.UpgradeImg.AltText": "yükseltme görseli", + "ViewLimitDialog.notifyAdmin.Success": "Yöneticiniz bilgilendirildi", "ViewTitle.hide-description": "açıklamayı gizle", "ViewTitle.pick-icon": "Simge seçin", "ViewTitle.random-icon": "Rastgele", "ViewTitle.remove-icon": "Simgeyi kaldır", "ViewTitle.show-description": "açıklamayı görüntüle", "ViewTitle.untitled-board": "Başlıksız pano", - "WelcomePage.Description": "Pano, alışılmış kanban panosu görünümünde takımların işleri tanımlamasını, düzenlemesini, izlemesi ve yönetmesini sağlayan bir proje yönetimi aracıdır", + "WelcomePage.Description": "Pano, alışılmış Kanban panosu görünümünde takımların işleri tanımlamasını, düzenlemesini, izlemesi ve yönetmesini sağlayan bir proje yönetimi aracıdır.", "WelcomePage.Explore.Button": "Tura çıkın", "WelcomePage.Heading": "Panolara hoş geldiniz", "WelcomePage.NoThanks.Text": "Hayır teşekkürler, kendim anlayacağım", - "Workspace.editing-board-template": "Bir tahta kalıbını düzenliyorsunuz.", + "Workspace.editing-board-template": "Bir pano kalıbını düzenliyorsunuz.", + "boardSelector.confirm-link-board": "Panoyu kanala bağla", + "boardSelector.confirm-link-board-button": "Panoyu bağla", + "boardSelector.confirm-link-board-subtext": "\"{boardName}\" panosunu kanala bağladığınızda, kanalın tüm üyeleri (var olan ve yeni) panoyu düzenleyebilir. Bir pano ile bir kanalın bağlantısını istediğiniz zaman kaldırabilirsiniz.", + "boardSelector.confirm-link-board-subtext-with-other-channel": "Bir panoyu bir kanala bağladığınızda, kanalın tüm üyeleri (var olan ve yeni) panoyu düzenleyebilir.{lineBreak}Bu pano şu anda başka bir kanal ile bağlantılı. Bu kanala bağlamayı seçerseniz diğer kanal ile bağlantısı kesilecek.", + "boardSelector.create-a-board": "Bir pano ekle", + "boardSelector.link": "Bağlantı", + "boardSelector.search-for-boards": "Pano arama", + "boardSelector.title": "Panoları bağla", + "boardSelector.unlink": "Bağlantıyı kaldır", "calendar.month": "Ay", "calendar.today": "Bugün", "calendar.week": "Hafta", + "cloudMessage.learn-more": "Ayrıntılı bilgi alın", "createImageBlock.failed": "Dosya yüklenemedi. Dosya boyutu sınırı aşıldı.", "default-properties.badges": "Yorumlar ve açıklama", "default-properties.title": "Başlık", + "error.back-to-home": "Girişe dön", + "error.back-to-team": "Takıma dön", + "error.board-not-found": "Pano bulunamadı.", + "error.go-login": "Oturum aç", + "error.invalid-read-only-board": "Bu panoya erişme izniniz yok. Panolara erişmek için oturum açın.", + "error.not-logged-in": "Oturumunuzun süresi dolmuş ya da oturum açmamışsınız. Panolara erişmek için yeniden oturum açın.", "error.page.title": "Bir şeyler ters gitti", + "error.team-undefined": "Geçerli bir takım değil.", + "error.unknown": "Bir sorun çıktı.", "generic.previous": "Önceki", "imagePaste.upload-failed": "Bazı dosyalar yüklenemedi. Dosya boyutu sınırı aşıldı", + "limitedCard.title": "Kartlar gizli", "login.log-in-button": "Oturum aç", "login.log-in-title": "Oturum açın", "login.register-button": "ya da hesabınız yoksa bir hesap açın", + "notification-box-card-limit-reached.close-tooltip": "10 gün için sustur", + "notification-box-card-limit-reached.contact-link": "yöneticinizi bilgilendirin", + "notification-box-card-limit-reached.link": "Ücretli bir tarifeye yükselt", + "notification-box-card-limit-reached.title": "panoda {cards} kart gizli", + "notification-box-cards-hidden.title": "Bu işlem başka bir kartı gizledi", + "notification-box.card-limit-reached.not-admin.text": "Arşivlenmiş kartlara erişmek için {contactLink} ile görüşerek ücretli bir tarifeye yükseltmesini isteyin.", + "notification-box.card-limit-reached.text": "Kart sınırına ulaşıldı. Eski kartları görüntülemek için {link}", "register.login-button": "ya da bir hesabınız varsa oturum açın", "register.signup-title": "Hesap açın", + "rhs-boards.add": "Ekle", + "rhs-boards.dm": "Dİ", + "rhs-boards.gm": "Gİ", + "rhs-boards.header.dm": "bu doğrudan ileti", + "rhs-boards.header.gm": "bu grup iletisi", + "rhs-boards.last-update-at": "Son güncelleme: {datetime}", + "rhs-boards.link-boards-to-channel": "Panoları {channelName} kanalına bağla", + "rhs-boards.linked-boards": "Bağlı panolar", + "rhs-boards.no-boards-linked-to-channel": "Henüz {channelName} kanalına bağlanmış bir pano yok", + "rhs-boards.no-boards-linked-to-channel-description": "Panolar, takımlar arasındaki çalışmaları tanımlamak, organize etmek, izlemek ve yönetmek için kullanılabilen kandan panosuna benzer bir proje yönetimi aracıdır.", + "rhs-boards.unlink-board": "Panonun bağlantısını kaldır", + "rhs-channel-boards-header.title": "Panolar", "share-board.publish": "Yayınla", "share-board.share": "Paylaş", + "shareBoard.channels-select-group": "Kanallar", + "shareBoard.confirm-link-channel": "Panoyu kanala bağla", + "shareBoard.confirm-link-channel-button": "Kanalı bağla", + "shareBoard.confirm-link-channel-button-with-other-channel": "Eski bağlantıyı kes ve bu kanala bağla", + "shareBoard.confirm-link-channel-subtext": "Bir kanalı bir panoya bağladığınızda, kanalın tüm üyeleri (var olan ve yeni) panoyu düzenleyebilir.", + "shareBoard.confirm-link-channel-subtext-with-other-channel": "Bir kanalı bir panoya bağladığınızda, kanalın tüm üyeleri (var olan ve yeni) panoyu düzenleyebilir.{lineBreak}Bu pano şu anda başka bir kanal ile bağlantılı. Bu kanala bağlamayı seçerseniz diğer kanal ile bağlantısı kesilecek.", + "shareBoard.confirm-link-public-channel-button": "Evet, herkese açık kanalı ekle", + "shareBoard.confirm-unlink.body": "Bir kanalın bir pano ile bağlantısını kaldırdığınızda, kanalın tüm üyeleri (var olan ve yeni), kendilerine özel olarak izin verilmedikçe, panoya erişimi kaybeder.", + "shareBoard.confirm-unlink.confirmBtnText": "Kanalın bağlantısını kaldır", + "shareBoard.confirm-unlink.title": "Kanalın pano ile bağlantısı kaldır", "shareBoard.lastAdmin": "Panoların en az bir yöneticisi olmalıdır", + "shareBoard.members-select-group": "Üyeler", "tutorial_tip.finish_tour": "Tamam", "tutorial_tip.got_it": "Anladım", "tutorial_tip.ok": "Sonraki", diff --git a/webapp/i18n/uk.json b/webapp/i18n/uk.json index 4c980d3fb..7e4807a74 100644 --- a/webapp/i18n/uk.json +++ b/webapp/i18n/uk.json @@ -4,7 +4,24 @@ "BoardComponent.hidden-columns": "Приховані стовпці", "BoardComponent.hide": "Приховати", "BoardComponent.new": "+ Створити", + "BoardComponent.no-property": "Немає {property}", "BoardComponent.show": "Показати", + "BoardMember.schemeAdmin": "Адміністратор", "BoardPage.newVersion": "Доступна оновлена версія Панелі, тицьни тут щоб оновити.", - "BoardPage.syncFailed": "Можливо Панель видалено або права анульовано." + "BoardPage.syncFailed": "Можливо Панель видалено або права анульовано.", + "BoardTemplateSelector.add-template": "Новий шаблон", + "BoardTemplateSelector.create-empty-board": "Створити порожню доску", + "BoardTemplateSelector.delete-template": "Видалити", + "BoardTemplateSelector.description": "Виберіть шаблон який допоможе розпочати. Ви можете налаштувати шаблон відповідно до ваших потреб, чи створити новий та налаштувати з нуля", + "BoardTemplateSelector.edit-template": "Редагувати", + "BoardTemplateSelector.plugin.no-content-title": "Створити Доску у {teamName}", + "BoardTemplateSelector.title": "Створити доску", + "BoardTemplateSelector.use-this-template": "Використати цей шаблон", + "BoardsUnfurl.Updated": "Оновлено {time}", + "Calculations.Options.count.displayName": "Кількість", + "Calculations.Options.count.label": "Кількість", + "generic.previous": "Попередній", + "tutorial_tip.ok": "Гаразд", + "tutorial_tip.out": "Відмовтеся від цих порад.", + "tutorial_tip.seen": "Ви бачили це раніше?" } diff --git a/webapp/i18n/vi.json b/webapp/i18n/vi.json new file mode 100644 index 000000000..ac66486ef --- /dev/null +++ b/webapp/i18n/vi.json @@ -0,0 +1,24 @@ +{ + "BoardComponent.add-a-group": "+ Thêm nhóm", + "BoardComponent.delete": "Xóa", + "BoardComponent.hidden-columns": "Cột ẩn", + "BoardComponent.hide": "Ẩn", + "BoardComponent.new": "+ Thêm", + "BoardComponent.no-property": "Không {property}", + "BoardComponent.show": "Hiện", + "BoardMember.schemeAdmin": "Quản trị", + "BoardMember.schemeCommenter": "Người bình luận", + "BoardMember.schemeEditor": "Người soạn thảo", + "BoardMember.schemeNone": "Không", + "BoardMember.schemeViewer": "Người xem", + "BoardMember.schemeViwer": "Người xem", + "BoardMember.unlinkChannel": "Gỡ liên kết", + "BoardPage.newVersion": "Có một phiên bản mới của bảng, click vào đây để nạp lại.", + "Calculations.Options.average.displayName": "Trung bình", + "Calculations.Options.average.label": "Trung bình", + "share-board.publish": "Công khai", + "share-board.share": "Chia sẻ", + "shareBoard.channels-select-group": "Kênh", + "shareBoard.members-select-group": "Thành viên", + "tutorial_tip.finish_tour": "Xong" +} diff --git a/webapp/i18n/zh_Hans.json b/webapp/i18n/zh_Hans.json index f2b789bfc..f8362f780 100644 --- a/webapp/i18n/zh_Hans.json +++ b/webapp/i18n/zh_Hans.json @@ -79,9 +79,6 @@ "CardDetailProperty.property-changed": "已成功修改属性!", "CardDetailProperty.property-deleted": "成功删除 {propertyName}!", "CardDetailProperty.property-name-change-subtext": "属性的类型从\"{oldPropType}\" 更改为\"{newPropType}\"", - "CardDetailProperty.property-type-change-subtext": "名称:\"{newPropName}\"", - "CardDialog.copiedLink": "已复制!", - "CardDialog.copyLink": "复制链接", "CardDialog.delete-confirmation-dialog-button-text": "删除", "CardDialog.delete-confirmation-dialog-heading": "确认删除卡片!", "CardDialog.editing-template": "您正在编辑模板。", @@ -130,22 +127,13 @@ "Filter.not-includes": "不包含", "FilterComponent.add-filter": "+ 增加过滤条件", "FilterComponent.delete": "删除", - "FindBoFindBoardsDialog.IntroText": "查找版块", "FindBoardsDialog.NoResultsFor": "没有\"{searchQuery}\"相关的结果", "FindBoardsDialog.NoResultsSubtext": "请检查拼写或者查找其他内容。", "FindBoardsDialog.SubTitle": "输入内容来查找板块。使用上/下浏览。ENTER选择,ESC取消", "FindBoardsDialog.Title": "查找板块", - "GalleryCard.copiedLink": "已复制!", - "GalleryCard.copyLink": "复制链接", - "GalleryCard.delete": "删除", - "GalleryCard.duplicate": "复制", "GroupBy.hideEmptyGroups": "隐藏{count}个空组", "GroupBy.showHiddenGroups": "显示已隐藏的{count}个组", "GroupBy.ungroup": "未分组", - "KanbanCard.copiedLink": "已复制!", - "KanbanCard.copyLink": "复制链接", - "KanbanCard.delete": "删除", - "KanbanCard.duplicate": "复制", "KanbanCard.untitled": "无标题", "Mutator.new-card-from-template": "使用模板新增卡片", "Mutator.new-template-from-card": "从卡片新增模板", @@ -265,7 +253,6 @@ "ViewHeader.new": "新", "ViewHeader.properties": "属性", "ViewHeader.properties-menu": "属性菜单", - "ViewHeader.search": "搜索", "ViewHeader.search-text": "搜索文本", "ViewHeader.select-a-template": "选择范本", "ViewHeader.set-default-template": "设为默认范本", diff --git a/webapp/i18n/zh_Hant.json b/webapp/i18n/zh_Hant.json index 275f74c2e..d0ae42d09 100644 --- a/webapp/i18n/zh_Hant.json +++ b/webapp/i18n/zh_Hant.json @@ -73,8 +73,6 @@ "CardDetailProperty.property-change-action-button": "變更屬性", "CardDetailProperty.property-changed": "已成功變更屬性!", "CardDetailProperty.property-deleted": "成功刪除 {propertyName}!", - "CardDialog.copiedLink": "已複製!", - "CardDialog.copyLink": "複製連結", "CardDialog.delete-confirmation-dialog-button-text": "刪除", "CardDialog.delete-confirmation-dialog-heading": "確認刪除卡片!", "CardDialog.editing-template": "您正在編輯範本。", @@ -119,18 +117,9 @@ "Filter.not-includes": "不包含", "FilterComponent.add-filter": "+ 增加過濾條件", "FilterComponent.delete": "刪除", - "FindBoFindBoardsDialog.IntroText": "搜尋看板", "FindBoardsDialog.NoResultsFor": "「{searchQuery}」搜尋未果", "FindBoardsDialog.Title": "尋找看板", - "GalleryCard.copiedLink": "已複製!", - "GalleryCard.copyLink": "複製連結", - "GalleryCard.delete": "刪除", - "GalleryCard.duplicate": "建立副本", "GroupBy.ungroup": "未分组", - "KanbanCard.copiedLink": "已複製!", - "KanbanCard.copyLink": "複製連結", - "KanbanCard.delete": "刪除", - "KanbanCard.duplicate": "建立副本", "KanbanCard.untitled": "無標題", "Mutator.new-card-from-template": "使用範本新增卡片", "Mutator.new-template-from-card": "從卡片新增範本", @@ -234,7 +223,6 @@ "ViewHeader.group-by": "以 {property} 分組", "ViewHeader.new": "新", "ViewHeader.properties": "屬性", - "ViewHeader.search": "搜尋", "ViewHeader.search-text": "搜尋文字", "ViewHeader.select-a-template": "選擇範本", "ViewHeader.set-default-template": "設為預設", diff --git a/webapp/package-lock.json b/webapp/package-lock.json index f32732107..be5c2a9f9 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -14767,9 +14767,9 @@ } }, "node_modules/terser": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.1.tgz", - "integrity": "sha512-+ahUAE+iheqBTDxXhTisdA8hgvbEG1hHOQ9xmNjeUJSoi6DU/gMrKNcfZjHkyY6Alnuyc+ikYJaxxfHkT3+WuQ==", + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.2", @@ -27171,9 +27171,9 @@ } }, "terser": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.1.tgz", - "integrity": "sha512-+ahUAE+iheqBTDxXhTisdA8hgvbEG1hHOQ9xmNjeUJSoi6DU/gMrKNcfZjHkyY6Alnuyc+ikYJaxxfHkT3+WuQ==", + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", "dev": true, "requires": { "@jridgewell/source-map": "^0.3.2", diff --git a/webapp/src/blocks/block.ts b/webapp/src/blocks/block.ts index 4105996bc..1c274fbea 100644 --- a/webapp/src/blocks/block.ts +++ b/webapp/src/blocks/block.ts @@ -13,7 +13,6 @@ type ContentBlockTypes = typeof contentBlockTypes[number] type BlockTypes = typeof blockTypes[number] interface BlockPatch { - boardId?: string parentId?: string schema?: number type?: BlockTypes diff --git a/webapp/src/blocks/board.ts b/webapp/src/blocks/board.ts index 5038c6af4..afceb844e 100644 --- a/webapp/src/blocks/board.ts +++ b/webapp/src/blocks/board.ts @@ -74,7 +74,7 @@ type BoardsAndBlocksPatch = { blockPatches: BlockPatch[], } -type PropertyType = 'text' | 'number' | 'select' | 'multiSelect' | 'date' | 'person' | 'file' | 'checkbox' | 'url' | 'email' | 'phone' | 'createdTime' | 'createdBy' | 'updatedTime' | 'updatedBy' +type PropertyTypeEnum = 'text' | 'number' | 'select' | 'multiSelect' | 'date' | 'person' | 'file' | 'checkbox' | 'url' | 'email' | 'phone' | 'createdTime' | 'createdBy' | 'updatedTime' | 'updatedBy' | 'unknown' interface IPropertyOption { id: string @@ -86,7 +86,7 @@ interface IPropertyOption { interface IPropertyTemplate { id: string name: string - type: PropertyType + type: PropertyTypeEnum options: IPropertyOption[] } @@ -310,7 +310,7 @@ export { BoardMember, BoardsAndBlocks, BoardsAndBlocksPatch, - PropertyType, + PropertyTypeEnum, IPropertyOption, IPropertyTemplate, BoardGroup, diff --git a/webapp/src/blocks/filterClause.ts b/webapp/src/blocks/filterClause.ts index 1cd6bd349..166b05c24 100644 --- a/webapp/src/blocks/filterClause.ts +++ b/webapp/src/blocks/filterClause.ts @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import {Utils} from '../utils' -type FilterCondition = 'includes' | 'notIncludes' | 'isEmpty' | 'isNotEmpty' +type FilterCondition = 'includes' | 'notIncludes' | 'isEmpty' | 'isNotEmpty' | 'isSet' | 'isNotSet' | 'is' | 'contains' | 'notContains' | 'startsWith' | 'notStartsWith' | 'endsWith' | 'notEndsWith' type FilterClause = { propertyId: string diff --git a/webapp/src/cardFilter.ts b/webapp/src/cardFilter.ts index 49dab2c47..2a229260b 100644 --- a/webapp/src/cardFilter.ts +++ b/webapp/src/cardFilter.ts @@ -44,7 +44,10 @@ class CardFilter { } static isClauseMet(filter: FilterClause, templates: readonly IPropertyTemplate[], card: Card): boolean { - const value = card.fields.properties[filter.propertyId] + let value = card.fields.properties[filter.propertyId] + if (filter.propertyId === 'title') { + value = card.title + } switch (filter.condition) { case 'includes': { if (filter.values?.length < 1) { @@ -64,6 +67,54 @@ class CardFilter { case 'isNotEmpty': { return (value || '').length > 0 } + case 'isSet': { + return Boolean(value) + } + case 'isNotSet': { + return !value + } + case 'is': { + if (filter.values.length === 0) { + return true + } + return filter.values[0] === value + } + case 'contains': { + if (filter.values.length === 0) { + return true + } + return (value as string || '').includes(filter.values[0]) + } + case 'notContains': { + if (filter.values.length === 0) { + return true + } + return (value as string || '').includes(filter.values[0]) + } + case 'startsWith': { + if (filter.values.length === 0) { + return true + } + return (value as string || '').startsWith(filter.values[0]) + } + case 'notStartsWith': { + if (filter.values.length === 0) { + return true + } + return !(value as string || '').startsWith(filter.values[0]) + } + case 'endsWith': { + if (filter.values.length === 0) { + return true + } + return (value as string || '').endsWith(filter.values[0]) + } + case 'notEndsWith': { + if (filter.values.length === 0) { + return true + } + return !(value as string || '').endsWith(filter.values[0]) + } default: { Utils.assertFailure(`Invalid filter condition ${filter.condition}`) } diff --git a/webapp/src/components/__snapshots__/cardDialog.test.tsx.snap b/webapp/src/components/__snapshots__/cardDialog.test.tsx.snap index c52890397..b89b51cd8 100644 --- a/webapp/src/components/__snapshots__/cardDialog.test.tsx.snap +++ b/webapp/src/components/__snapshots__/cardDialog.test.tsx.snap @@ -645,7 +645,7 @@ exports[`components/cardDialog return cardDialog menu content 1`] = ` class="d-flex" >
+
@@ -515,39 +493,13 @@ exports[`components/centerPanel Clicking on the Hidden card count should open a
-
+
@@ -599,18 +567,12 @@ exports[`components/centerPanel Clicking on the Hidden card count should open a style="width: 100px;" > hide description @@ -1379,7 +1341,7 @@ exports[`components/centerPanel return centerPanel and click on new card to edit type="button" > hide description @@ -1690,18 +1652,12 @@ exports[`components/centerPanel return centerPanel and click on new card to edit style="width: 100px;" > -
+
@@ -1898,39 +1838,13 @@ exports[`components/centerPanel return centerPanel and click on new card to edit
-
+
@@ -1982,18 +1912,12 @@ exports[`components/centerPanel return centerPanel and click on new card to edit style="width: 100px;" > -
- - -
-
- - -
hide description @@ -2879,18 +2709,12 @@ exports[`components/centerPanel return centerPanel and press touch ctrl+d for on style="width: 100px;" > -
+
@@ -3087,39 +2895,13 @@ exports[`components/centerPanel return centerPanel and press touch ctrl+d for on
-
+
@@ -3171,18 +2969,12 @@ exports[`components/centerPanel return centerPanel and press touch ctrl+d for on style="width: 100px;" > hide description @@ -3565,18 +3357,12 @@ exports[`components/centerPanel return centerPanel and press touch del for one c style="width: 100px;" > -
+
@@ -3773,39 +3543,13 @@ exports[`components/centerPanel return centerPanel and press touch del for one c
-
+
@@ -3857,18 +3617,12 @@ exports[`components/centerPanel return centerPanel and press touch del for one c style="width: 100px;" > hide description @@ -4251,18 +4005,12 @@ exports[`components/centerPanel return centerPanel and press touch esc for one c style="width: 100px;" > -
+
@@ -4459,39 +4191,13 @@ exports[`components/centerPanel return centerPanel and press touch esc for one c
-
+
@@ -4543,18 +4265,12 @@ exports[`components/centerPanel return centerPanel and press touch esc for one c style="width: 100px;" > hide description @@ -4937,18 +4653,12 @@ exports[`components/centerPanel return centerPanel and press touch esc for one c style="width: 100px;" > -
+
@@ -5145,39 +4839,13 @@ exports[`components/centerPanel return centerPanel and press touch esc for one c
-
+
@@ -5229,18 +4913,12 @@ exports[`components/centerPanel return centerPanel and press touch esc for one c style="width: 100px;" > hide description @@ -5623,18 +5301,12 @@ exports[`components/centerPanel return centerPanel and press touch esc for two c style="width: 100px;" > -
+
@@ -5831,39 +5487,13 @@ exports[`components/centerPanel return centerPanel and press touch esc for two c
-
+
@@ -5915,18 +5561,12 @@ exports[`components/centerPanel return centerPanel and press touch esc for two c style="width: 100px;" > hide description @@ -6309,18 +5949,12 @@ exports[`components/centerPanel return centerPanel and press touch esc for two c style="width: 100px;" > -
+
@@ -6517,39 +6135,13 @@ exports[`components/centerPanel return centerPanel and press touch esc for two c
-
+
@@ -6601,18 +6209,12 @@ exports[`components/centerPanel return centerPanel and press touch esc for two c style="width: 100px;" > hide description @@ -6995,18 +6597,12 @@ exports[`components/centerPanel return centerPanel and press touch esc for two c style="width: 100px;" > -
+
@@ -7203,39 +6783,13 @@ exports[`components/centerPanel return centerPanel and press touch esc for two c
-
+
@@ -7287,18 +6857,12 @@ exports[`components/centerPanel return centerPanel and press touch esc for two c style="width: 100px;" > hide description @@ -7681,18 +7245,12 @@ exports[`components/centerPanel return centerPanel and select one card and click style="width: 100px;" > -
+
@@ -7889,39 +7431,13 @@ exports[`components/centerPanel return centerPanel and select one card and click
-
+
@@ -7973,18 +7505,12 @@ exports[`components/centerPanel return centerPanel and select one card and click style="width: 100px;" > hide description @@ -8367,18 +7893,12 @@ exports[`components/centerPanel return centerPanel and select one card and click style="width: 100px;" > -
+
@@ -8575,39 +8079,13 @@ exports[`components/centerPanel return centerPanel and select one card and click
-
+
@@ -8659,18 +8153,12 @@ exports[`components/centerPanel return centerPanel and select one card and click style="width: 100px;" > hide description @@ -9064,7 +8552,7 @@ exports[`components/centerPanel should match snapshot for Kanban 1`] = ` type="button" > hide description @@ -9583,7 +9071,7 @@ exports[`components/centerPanel should match snapshot for Kanban, not shared 1`] type="button" > hide description @@ -10102,7 +9590,7 @@ exports[`components/centerPanel should match snapshot for Table 1`] = ` type="button" > hide description @@ -10366,18 +9854,12 @@ exports[`components/centerPanel should match snapshot for Table 1`] = ` style="width: 100px;" > -
+
@@ -10580,18 +10046,12 @@ exports[`components/centerPanel should match snapshot for Table 1`] = ` style="width: 100px;" >
`; - -exports[`components/propertyValueElement should match snapshot, url, array value 2`] = ` -
-
- - http://localhost - - - -
-
-`; diff --git a/webapp/src/components/__snapshots__/viewTitle.test.tsx.snap b/webapp/src/components/__snapshots__/viewTitle.test.tsx.snap index d13f025d6..606e953a5 100644 --- a/webapp/src/components/__snapshots__/viewTitle.test.tsx.snap +++ b/webapp/src/components/__snapshots__/viewTitle.test.tsx.snap @@ -12,7 +12,7 @@ exports[`components/viewTitle add random icon 1`] = ` type="button" > Add icon @@ -22,7 +22,7 @@ exports[`components/viewTitle add random icon 1`] = ` type="button" > show description @@ -56,7 +56,7 @@ exports[`components/viewTitle hide description 1`] = ` type="button" > show description @@ -107,7 +107,7 @@ exports[`components/viewTitle should match snapshot 1`] = ` type="button" > hide description @@ -217,7 +217,7 @@ exports[`components/viewTitle show description 1`] = ` type="button" > hide description diff --git a/webapp/src/components/__snapshots__/workspace.test.tsx.snap b/webapp/src/components/__snapshots__/workspace.test.tsx.snap index 33edc5075..1aef03022 100644 --- a/webapp/src/components/__snapshots__/workspace.test.tsx.snap +++ b/webapp/src/components/__snapshots__/workspace.test.tsx.snap @@ -125,19 +125,26 @@ exports[`src/components/workspace return workspace and showcard 1`] = ` class="CompassIcon icon-chevron-down ChevronDownIcon" /> Category 1 +
board title
-
Boards +
hide description @@ -1250,19 +1266,26 @@ exports[`src/components/workspace should match snapshot 1`] = ` class="CompassIcon icon-chevron-down ChevronDownIcon" /> Category 1 +
board title
-
Boards +
hide description diff --git a/webapp/src/components/boardTemplateSelector/__snapshots__/boardTemplateSelector.test.tsx.snap b/webapp/src/components/boardTemplateSelector/__snapshots__/boardTemplateSelector.test.tsx.snap index f2b48f487..81d1377c0 100644 --- a/webapp/src/components/boardTemplateSelector/__snapshots__/boardTemplateSelector.test.tsx.snap +++ b/webapp/src/components/boardTemplateSelector/__snapshots__/boardTemplateSelector.test.tsx.snap @@ -29,7 +29,7 @@ exports[`components/boardTemplateSelector/boardTemplateSelector a focalboard Plu

- Choose a template to help you get started. Easily customize the template to fit your needs, or create an empty board to start from scratch. + Add a board to the sidebar using any of the templates defined below or start from scratch.

- Choose a template to help you get started. Easily customize the template to fit your needs, or create an empty board to start from scratch. + Add a board to the sidebar using any of the templates defined below or start from scratch.

- Choose a template to help you get started. Easily customize the template to fit your needs, or create an empty board to start from scratch. + Add a board to the sidebar using any of the templates defined below or start from scratch.

{ userEvent.click(divNewTemplate!) expect(mockedMutator.addEmptyBoardTemplate).toBeCalledTimes(1) }) - test('return BoardTemplateSelector and click empty board', () => { + test('return BoardTemplateSelector and click empty board', async () => { + const newBoard = createBoard({id: 'new-board'} as Board) + mockedMutator.addEmptyBoard.mockResolvedValue({boards: [newBoard], blocks: []}) + render(wrapDNDIntl( , ), {wrapper: MemoryRouter}) + const divEmptyboard = screen.getByText('Create empty board').parentElement expect(divEmptyboard).not.toBeNull() userEvent.click(divEmptyboard!) expect(mockedMutator.addEmptyBoard).toBeCalledTimes(1) + await waitFor(() => expect(mockedMutator.updateBoard).toBeCalledWith(newBoard, newBoard, 'linked channel')) }) test('return BoardTemplateSelector and click delete template icon', async () => { const root = document.createElement('div') @@ -279,6 +285,9 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => { userEvent.click(editIcon!) }) test('return BoardTemplateSelector and click to add board from template', async () => { + const newBoard = createBoard({id: 'new-board'} as Board) + mockedMutator.addBoardFromTemplate.mockResolvedValue({boards: [newBoard], blocks: []}) + render(wrapDNDIntl( @@ -300,8 +309,44 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => { await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1)) await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), '1', team1.id)) + await waitFor(() => expect(mockedMutator.updateBoard).toBeCalledWith(newBoard, newBoard, 'linked channel')) }) + + test('return BoardTemplateSelector and click to add board from template with channelId', async () => { + const newBoard = createBoard({id: 'new-board'} as Board) + mockedMutator.addBoardFromTemplate.mockResolvedValue({boards: [newBoard], blocks: []}) + + render(wrapDNDIntl( + + + + , + ), {wrapper: MemoryRouter}) + const divBoardToSelect = screen.getByText(template1Title).parentElement + expect(divBoardToSelect).not.toBeNull() + + act(() => { + userEvent.click(divBoardToSelect!) + }) + + const useTemplateButton = screen.getByText('Use this template').parentElement + expect(useTemplateButton).not.toBeNull() + act(() => { + userEvent.click(useTemplateButton!) + }) + + await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1)) + await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), '1', team1.id)) + await waitFor(() => expect(mockedMutator.updateBoard).toBeCalledWith({...newBoard, channelId: 'test-channel'}, newBoard, 'linked channel')) + }) + test('return BoardTemplateSelector and click to add board from global template', async () => { + const newBoard = createBoard({id: 'new-board'} as Board) + mockedMutator.addBoardFromTemplate.mockResolvedValue({boards: [newBoard], blocks: []}) + render(wrapDNDIntl( @@ -323,8 +368,12 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => { await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1)) await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), 'global-1', team1.id)) await waitFor(() => expect(mockedTelemetry.trackEvent).toBeCalledWith('boards', 'createBoardViaTemplate', {boardTemplateId: 'template_id_global'})) + await waitFor(() => expect(mockedMutator.updateBoard).toBeCalledWith(newBoard, newBoard, 'linked channel')) }) test('should start product tour on choosing welcome template', async () => { + const newBoard = createBoard({id: 'new-board'} as Board) + mockedMutator.addBoardFromTemplate.mockResolvedValue({boards: [newBoard], blocks: []}) + render(wrapDNDIntl( @@ -347,6 +396,7 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => { await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1)) await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), '2', team1.id)) await waitFor(() => expect(mockedTelemetry.trackEvent).toBeCalledWith('boards', 'createBoardViaTemplate', {boardTemplateId: 'template_id_2'})) + await waitFor(() => expect(mockedMutator.updateBoard).toBeCalledWith(newBoard, newBoard, 'linked channel')) expect(mockedOctoClient.patchUserConfig).toBeCalledWith('user-id-1', { updatedFields: { 'focalboard_onboardingTourStarted': '1', diff --git a/webapp/src/components/boardTemplateSelector/boardTemplateSelector.tsx b/webapp/src/components/boardTemplateSelector/boardTemplateSelector.tsx index 6c2b2c98d..172df5161 100644 --- a/webapp/src/components/boardTemplateSelector/boardTemplateSelector.tsx +++ b/webapp/src/components/boardTemplateSelector/boardTemplateSelector.tsx @@ -34,6 +34,7 @@ type Props = { title?: React.ReactNode description?: React.ReactNode onClose?: () => void + channelId?: string } const BoardTemplateSelector = (props: Props) => { @@ -99,10 +100,12 @@ const BoardTemplateSelector = (props: Props) => { const handleUseTemplate = async () => { if (activeTemplate.teamId === '0') { - TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.CreateBoardViaTemplate, {boardTemplateId: activeTemplate.properties.trackingTemplateId as string}) + TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.CreateBoardViaTemplate, {boardTemplateId: activeTemplate.properties.trackingTemplateId as string, channelID: props.channelId}) } - await mutator.addBoardFromTemplate(currentTeam?.id || Constants.globalTeamId, intl, showBoard, () => showBoard(currentBoardId), activeTemplate.id, currentTeam?.id) + const boardsAndBlocks = await mutator.addBoardFromTemplate(currentTeam?.id || Constants.globalTeamId, intl, showBoard, () => showBoard(currentBoardId), activeTemplate.id, currentTeam?.id) + const board = boardsAndBlocks.boards[0] + await mutator.updateBoard({...board, channelId: props.channelId || ''}, board, 'linked channel') if (activeTemplate.title === OnboardingBoardTitle) { resetTour() } @@ -144,7 +147,7 @@ const BoardTemplateSelector = (props: Props) => { {description || ( )}

@@ -193,7 +196,11 @@ const BoardTemplateSelector = (props: Props) => { filled={false} emphasis={'secondary'} size={'medium'} - onClick={() => mutator.addEmptyBoard(currentTeam?.id || '', intl, showBoard, () => showBoard(currentBoardId))} + onClick={async () => { + const boardsAndBlocks = await mutator.addEmptyBoard(currentTeam?.id || '', intl, showBoard, () => showBoard(currentBoardId)) + const board = boardsAndBlocks.boards[0] + await mutator.updateBoard({...board, channelId: props.channelId || ''}, board, 'linked channel') + }} > { className={isActive ? 'BoardTemplateSelectorItem active' : 'BoardTemplateSelectorItem'} onClick={onClickHandler} > - {template.icon} - {template.title} + {template.icon || } + {template.title || intl.formatMessage({id: 'View.NewTemplateTitle', defaultMessage: 'Untitled'})} {/* don't show template menu options for default templates */} {template.teamId !== Constants.globalTeamId &&
- + > } title={intl.formatMessage({id: 'BoardTemplateSelector.delete-template', defaultMessage: 'Delete'})} @@ -59,11 +60,11 @@ const BoardTemplateSelectorItem = (props: Props) => { }} /> - + > } title={intl.formatMessage({id: 'BoardTemplateSelector.edit-template', defaultMessage: 'Edit'})} diff --git a/webapp/src/components/boardTemplateSelector/boardTemplateSelectorPreview.tsx b/webapp/src/components/boardTemplateSelector/boardTemplateSelectorPreview.tsx index 2d061fec7..93493bfb1 100644 --- a/webapp/src/components/boardTemplateSelector/boardTemplateSelectorPreview.tsx +++ b/webapp/src/components/boardTemplateSelector/boardTemplateSelectorPreview.tsx @@ -104,6 +104,7 @@ const BoardTemplateSelectorPreview = (props: Props) => { readonly={false} onCardClicked={() => null} addCard={() => Promise.resolve()} + addCardFromTemplate={() => Promise.resolve()} showCard={() => null} hiddenCardsCount={0} showHiddenCardCountNotification={() => null} diff --git a/webapp/src/components/boardsSwitcher/boardsSwitcher.scss b/webapp/src/components/boardsSwitcher/boardsSwitcher.scss index 458dd53d3..309cc2b98 100644 --- a/webapp/src/components/boardsSwitcher/boardsSwitcher.scss +++ b/webapp/src/components/boardsSwitcher/boardsSwitcher.scss @@ -31,5 +31,6 @@ margin-left: 4px; width: 28px; height: 28px; + flex: 0 0 28px; } } diff --git a/webapp/src/components/boardsSwitcher/boardsSwitcher.tsx b/webapp/src/components/boardsSwitcher/boardsSwitcher.tsx index 84bad5937..d07ec61a8 100644 --- a/webapp/src/components/boardsSwitcher/boardsSwitcher.tsx +++ b/webapp/src/components/boardsSwitcher/boardsSwitcher.tsx @@ -6,13 +6,24 @@ import {useIntl} from 'react-intl' import Search from '../../widgets/icons/search' +import {useAppSelector} from '../../store/hooks' + +import { + getOnboardingTourCategory, + getOnboardingTourStep, +} from '../../store/users' + +import {getCurrentCard} from '../../store/cards' + import './boardsSwitcher.scss' import AddIcon from '../../widgets/icons/add' import BoardSwitcherDialog from '../boardsSwitcherDialog/boardSwitcherDialog' import {Utils} from '../../utils' import {Constants} from '../../constants' +import {TOUR_SIDEBAR, SidebarTourSteps} from '../../components/onboardingTour' import IconButton from '../../widgets/buttons/iconButton' +import SearchForBoardsTourStep from '../../components/onboardingTour/searchForBoards/searchForBoards' type Props = { onBoardTemplateSelectorOpen?: () => void, @@ -22,6 +33,15 @@ const BoardsSwitcher = (props: Props): JSX.Element => { const intl = useIntl() const [showSwitcher, setShowSwitcher] = useState(false) + const onboardingTourCategory = useAppSelector(getOnboardingTourCategory) + const onboardingTourStep = useAppSelector(getOnboardingTourStep) + const currentCard = useAppSelector(getCurrentCard) + const noCardOpen = !currentCard + + + const shouldViewSearchForBoardsTour = noCardOpen && + onboardingTourCategory === TOUR_SIDEBAR && + onboardingTourStep === SidebarTourSteps.SEARCH_FOR_BOARDS.toString() // We need this keyboard handling (copied from Mattermost webapp) instead of // using react-hotkeys-hook as react-hotkeys-hook is unable to handle keyboard shortcuts that @@ -40,12 +60,21 @@ const BoardsSwitcher = (props: Props): JSX.Element => { } } + const handleEscKeyPress = (e: KeyboardEvent) => { + if (Utils.isKeyPressed(e, Constants.keyCodes.ESC)) { + e.preventDefault() + setShowSwitcher(false) + } + } + useEffect(() => { document.addEventListener('keydown', handleQuickSwitchKeyPress) + document.addEventListener('keydown', handleEscKeyPress) // cleanup function return () => { document.removeEventListener('keydown', handleQuickSwitchKeyPress) + document.removeEventListener('keydown', handleEscKeyPress) } }, []) @@ -62,7 +91,7 @@ const BoardsSwitcher = (props: Props): JSX.Element => {
- + {shouldViewSearchForBoardsTour &&
} { Utils.isFocalboardPlugin() && { { showSwitcher && - setShowSwitcher(false)}/> + setShowSwitcher(false)} /> }
) diff --git a/webapp/src/components/boardsSwitcherDialog/boardSwitcherDialog.scss b/webapp/src/components/boardsSwitcherDialog/boardSwitcherDialog.scss index c1cf290b7..c93cc766c 100644 --- a/webapp/src/components/boardsSwitcherDialog/boardSwitcherDialog.scss +++ b/webapp/src/components/boardsSwitcherDialog/boardSwitcherDialog.scss @@ -7,7 +7,7 @@ .CompassIcon { font-size: 18px; - color: #484848; + color: rgba(var(--center-channel-color-rgb), 0.56); } .toolbar { diff --git a/webapp/src/components/boardsSwitcherDialog/boardSwitcherDialog.tsx b/webapp/src/components/boardsSwitcherDialog/boardSwitcherDialog.tsx index 5823e5bec..d8e1ed2f8 100644 --- a/webapp/src/components/boardsSwitcherDialog/boardSwitcherDialog.tsx +++ b/webapp/src/components/boardsSwitcherDialog/boardSwitcherDialog.tsx @@ -1,6 +1,6 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {ReactNode} from 'react' +import React, {ReactNode, useRef, createRef, useState, useEffect, MutableRefObject} from 'react' import './boardSwitcherDialog.scss' import {useIntl} from 'react-intl' @@ -14,13 +14,20 @@ import LockOutline from '../../widgets/icons/lockOutline' import {useAppSelector} from '../../store/hooks' import {getAllTeams, getCurrentTeam, Team} from '../../store/teams' import {getMe} from '../../store/users' +import {Utils} from '../../utils' import {BoardTypeOpen, BoardTypePrivate} from '../../blocks/board' +import { Constants } from '../../constants' type Props = { onClose: () => void } const BoardSwitcherDialog = (props: Props): JSX.Element => { + const [selected, setSelected] = useState(-1) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const [refs, setRefs] = useState>(useRef([])) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const [IDs, setIDs] = useState({}) const intl = useIntl() const team = useAppSelector(getCurrentTeam) const me = useAppSelector(getMe) @@ -42,7 +49,7 @@ const BoardSwitcherDialog = (props: Props): JSX.Element => { if (!me) { return } - const newPath = generatePath(match.path, {...match.params, teamId, boardId, viewId: undefined}) + const newPath = generatePath(Utils.getBoardPagePath(match.path), {...match.params, teamId, boardId, viewId: undefined}) history.push(newPath) props.onClose() } @@ -57,14 +64,22 @@ const BoardSwitcherDialog = (props: Props): JSX.Element => { const items = await octoClient.searchAll(query) const untitledBoardTitle = intl.formatMessage({id: 'ViewTitle.untitled-board', defaultMessage: 'Untitled board'}) - return items.map((item) => { + refs.current = items.map((_, i) => refs.current[i] ?? createRef()) + setRefs(refs) + return items.map((item, i) => { const resultTitle = item.title || untitledBoardTitle const teamTitle = teamsById[item.teamId].title + // eslint-disable-next-line @typescript-eslint/no-explicit-any + setIDs((prevIDs: any) => ({ + ...prevIDs, + [i]: [item.teamId, item.id] + })) return (
selectBoard(item.teamId, item.id)} + ref={refs.current[i]} > {item.type === BoardTypeOpen && } {item.type === BoardTypePrivate && } @@ -75,12 +90,34 @@ const BoardSwitcherDialog = (props: Props): JSX.Element => { }) } + const handleEnterKeyPress = (e: KeyboardEvent) => { + if (Utils.isKeyPressed(e, Constants.keyCodes.ENTER) && selected > -1) { + e.preventDefault() + const [teamId, id] = IDs[selected] + selectBoard(teamId, id) + } + } + + useEffect(() => { + if (selected >= 0) + refs.current[selected].current.parentElement.focus() + + document.addEventListener('keydown', handleEnterKeyPress) + + // cleanup function + return () => { + document.removeEventListener('keydown', handleEnterKeyPress) + } + }, [selected, refs, IDs]) + return ( setSelected(n)} /> ) } diff --git a/webapp/src/components/calculations/calculations.ts b/webapp/src/components/calculations/calculations.ts index fadfe8aab..47d4a2c63 100644 --- a/webapp/src/components/calculations/calculations.ts +++ b/webapp/src/components/calculations/calculations.ts @@ -9,7 +9,7 @@ import {Card} from '../../blocks/card' import {IPropertyTemplate} from '../../blocks/board' import {Utils} from '../../utils' import {Constants} from '../../constants' -import {DateProperty} from '../properties/dateRange/dateRange' +import {DateProperty} from '../../properties/date/date' const ROUNDED_DECIMAL_PLACES = 2 diff --git a/webapp/src/components/calculations/options.tsx b/webapp/src/components/calculations/options.tsx index 063d0b1f2..9972468e4 100644 --- a/webapp/src/components/calculations/options.tsx +++ b/webapp/src/components/calculations/options.tsx @@ -12,7 +12,7 @@ import {getSelectBaseStyle} from '../../theme' import ChevronUp from '../../widgets/icons/chevronUp' import {IPropertyTemplate} from '../../blocks/board' -type Option = { +export type Option = { label: string value: string displayName: string @@ -160,7 +160,7 @@ const DropdownIndicator = (props: DropdownIndicatorProps) => { } // Calculation option props shared by all implementations of calculation options -type CommonCalculationOptionProps = { +export type CommonCalculationOptionProps = { value: string, menuOpen: boolean onClose?: () => void @@ -174,7 +174,7 @@ type BaseCalculationOptionProps = CommonCalculationOptionProps & { options: Option[] } -const CalculationOptions = (props: BaseCalculationOptionProps): JSX.Element => { +export const CalculationOptions = (props: BaseCalculationOptionProps): JSX.Element => { const intl = useIntl() return ( @@ -208,9 +208,3 @@ const CalculationOptions = (props: BaseCalculationOptionProps): JSX.Element => { /> ) } - -export { - CalculationOptions, - Option, - CommonCalculationOptionProps, -} diff --git a/webapp/src/components/calendar/fullCalendar.tsx b/webapp/src/components/calendar/fullCalendar.tsx index b6c7ec678..5a9b56557 100644 --- a/webapp/src/components/calendar/fullCalendar.tsx +++ b/webapp/src/components/calendar/fullCalendar.tsx @@ -14,7 +14,8 @@ import mutator from '../../mutator' import {Board, IPropertyTemplate} from '../../blocks/board' import {BoardView} from '../../blocks/boardView' import {Card} from '../../blocks/card' -import {DateProperty, createDatePropertyFromString} from '../properties/dateRange/dateRange' +import {DateProperty} from '../../properties/date/date' +import propsRegistry from '../../properties' import Tooltip from '../../widgets/tooltip' import PropertyValueElement from '../propertyValueElement' import {Constants, Permission} from '../../constants' @@ -84,7 +85,7 @@ const CalendarFullView = (props: Props): JSX.Element|null => { } const isEditable = useCallback(() : boolean => { - if (readonly || !dateDisplayProperty || (dateDisplayProperty.type === 'createdTime' || dateDisplayProperty.type === 'updatedTime')) { + if (readonly || !dateDisplayProperty || propsRegistry.get(dateDisplayProperty.type).isReadOnly) { return false } return true @@ -92,23 +93,12 @@ const CalendarFullView = (props: Props): JSX.Element|null => { const myEventsList = useMemo(() => ( cards.flatMap((card): EventInput[] => { + const property = propsRegistry.get(dateDisplayProperty?.type || 'unknown') let dateFrom = new Date(card.createAt || 0) let dateTo = new Date(card.createAt || 0) - if (dateDisplayProperty && dateDisplayProperty?.type === 'updatedTime') { - dateFrom = new Date(card.updateAt || 0) - dateTo = new Date(card.updateAt || 0) - } else if (dateDisplayProperty && dateDisplayProperty?.type !== 'createdTime') { - const dateProperty = createDatePropertyFromString(card.fields.properties[dateDisplayProperty.id || ''] as string) - if (!dateProperty.from) { - return [] - } - - // date properties are stored as 12 pm UTC, convert to 12 am (00) UTC for calendar - dateFrom = dateProperty.from ? new Date(dateProperty.from + (dateProperty.includeTime ? 0 : timeZoneOffset(dateProperty.from))) : new Date() - dateFrom.setHours(0, 0, 0, 0) - const dateToNumber = dateProperty.to ? dateProperty.to + (dateProperty.includeTime ? 0 : timeZoneOffset(dateProperty.to)) : dateFrom.getTime() - dateTo = new Date(dateToNumber + oneDay) // Add one day. - dateTo.setHours(0, 0, 0, 0) + if (property.isDate && property.getDateFrom && property.getDateTo) { + dateFrom = property.getDateFrom(card.fields.properties[dateDisplayProperty?.id || ''], card) + dateTo = property.getDateTo(card.fields.properties[dateDisplayProperty?.id || ''], card) } return [{ id: card.id, @@ -257,7 +247,6 @@ const CalendarFullView = (props: Props): JSX.Element|null => { buttonText={buttonText} eventContent={renderEventContent} eventChange={eventChange} - selectable={isSelectable} selectMirror={true} select={onNewEvent} diff --git a/webapp/src/components/calendar/fullcalendar.scss b/webapp/src/components/calendar/fullcalendar.scss index 48bef7741..6d7edc5ae 100644 --- a/webapp/src/components/calendar/fullcalendar.scss +++ b/webapp/src/components/calendar/fullcalendar.scss @@ -5,6 +5,11 @@ margin-bottom: 10px; overflow: auto; + .fc-daygrid-event, + .fc-event-main { + @include z-index(unset); + } + .fc-daygrid-event, .fc-daygrid-day-number { text-decoration: none; diff --git a/webapp/src/components/cardDetail/__snapshots__/cardDetailContentsMenu.test.tsx.snap b/webapp/src/components/cardDetail/__snapshots__/cardDetailContentsMenu.test.tsx.snap index 945bfa836..2fcf4ed50 100644 --- a/webapp/src/components/cardDetail/__snapshots__/cardDetailContentsMenu.test.tsx.snap +++ b/webapp/src/components/cardDetail/__snapshots__/cardDetailContentsMenu.test.tsx.snap @@ -376,6 +376,162 @@ exports[`components/cardDetail/cardDetailContentsMenu return cardDetailContentsM Add content +
+
+
+ + + +
+ + Dunder Mifflin Party Planing Committee + +
+
+
+ +
+
+
+
+ + + +
+ + Dunder Mifflin Party Planing Committee + +
+
+
+ +
+
+
+
+ + Share Template + +
+
+
+ +
+
+
+`; + +exports[`src/components/shareBoard/shareBoard return shareBoard template and click Select 2`] = ` +
+
+
+
+ +
+
+`; + exports[`src/components/shareBoard/shareBoard return shareBoard, and click switch 1`] = `
- Search for people + Search for people and channels
@@ -2209,7 +3117,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoardComponent and cli class=" css-14el2xx-placeholder" id="react-select-7-placeholder" > - Search for people + Search for people and channels
@@ -2441,7 +3349,7 @@ exports[`src/components/shareBoard/shareBoard should match snapshot 1`] = ` class=" css-14el2xx-placeholder" id="react-select-2-placeholder" > - Search for people + Search for people and channels
`; + +exports[`src/components/shareBoard/shareBoard should match snapshot, with template 1`] = ` +
+
+
+
+ +
+
+`; diff --git a/webapp/src/components/shareBoard/__snapshots__/teamPermissionsRow.test.tsx.snap b/webapp/src/components/shareBoard/__snapshots__/teamPermissionsRow.test.tsx.snap new file mode 100644 index 000000000..ac8cb1819 --- /dev/null +++ b/webapp/src/components/shareBoard/__snapshots__/teamPermissionsRow.test.tsx.snap @@ -0,0 +1,485 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`src/components/shareBoard/teamPermissionsRow should match snapshot 1`] = ` +
+
+
+
+ + Everyone at Test Team Team + +
+
+
+