diff --git a/.github/codecov.yml b/.github/codecov.yml index 2523e4763..35629e99b 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -1,3 +1,10 @@ +comment: false + +coverage: + status: + patch: false + project: false + ignore: - "**/mock_*.go" - "**/*_test.go" diff --git a/.github/mergify.yml b/.github/mergify.yml new file mode 100644 index 000000000..efc77a0df --- /dev/null +++ b/.github/mergify.yml @@ -0,0 +1,33 @@ +merge_queue: + max_parallel_checks: 1 + queued_label: "ci:queued" + dequeued_label: "ci:dequeued" + +queue_rules: + - name: renovate + update_method: merge + +pull_request_rules: + - name: label conflicts + conditions: + - conflict + actions: + label: + toggle: + - conflict + + - name: Automatic merge on approval + conditions: + - or: + - "#approved-reviews-by>=1" + - label=ci:auto-merge + - or: + - "- head*=renovate/lockfile-maintenance-*" + - "created-at<=24h ago" + - base=master + - check-success=docker + - check-success=test + - check-success=lint + actions: + queue: + name: renovate diff --git a/.github/renovate.json b/.github/renovate.json index 792acd27d..4234cc9bf 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,24 +1,29 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["local>Trim21/renovate-config", "local>Trim21/renovate-config:monthly"], - "separateMinorPatch": false, - "separateMajorMinor": true, - "separateMultipleMajor": true, - "postUpdateOptions": ["gomodTidy1.17"], + "labels": ["ci:auto-merge", "dependencies"], + "rebaseWhen": "conflicted", + "prConcurrentLimit": 3, + "extends": [ + "local>Trim21/renovate-config", + "local>Trim21/renovate-config:go", + "local>Trim21/renovate-config:app", + "local>Trim21/renovate-config:monthly" + ], + "dockerfile": { + "managerFilePatterns": ["**/dockerfile"], + "enabled": true + }, "packageRules": [ { - "matchManagers": ["docker-compose"], - "matchPackageNames": ["mysql"], - "enabled": false + "matchDatasources": ["docker"], + "pinDigests": true, + "matchPackageNames": ["gcr.io/**"] }, { - "matchPackageNames": ["go"], + "matchManagers": ["docker-compose"], + "matchPackageNames": ["mysql"], "enabled": false }, - { - "matchManagers": ["gomod"], - "semanticCommitType": "build" - }, { "groupName": "npm", "matchManagers": ["npm"], diff --git a/.github/scripts/write-version.js b/.github/scripts/write-version.js index 5f59c3f31..f5665077c 100644 --- a/.github/scripts/write-version.js +++ b/.github/scripts/write-version.js @@ -21,11 +21,11 @@ const { execFileSync } = require("child_process"); const pkg = require("../../package.json"); function getVersion() { - if (process.env.TAG) { - return process.env.TAG; + if (process.env.SHA) { + return process.env.SHA; } - return pkg.version; + return "v" + pkg.version; } const version = getVersion(); @@ -47,7 +47,7 @@ function writeVersionFile() { "", `const Version = "${version}"`, "", - ].join("\n") + ].join("\n"), ); } diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 722d5bfe0..514e7d2b4 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -2,8 +2,8 @@ name: Build on: push: - branches-ignore: - - renovate/** + branches: + - master paths: - ".github/workflows/build.yaml" - "go.mod" @@ -24,37 +24,27 @@ on: - "**.go.json" - "etc/Dockerfile" +env: + GOTOOLCHAIN: "local" + jobs: docker: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v3 - - - name: Install Go - uses: actions/setup-go@v4 + - uses: actions/checkout@v6.0.2 with: - go-version-file: "go.mod" - cache: false + submodules: true - - name: Go Build Cache (build) - uses: actions/cache@v3 + - uses: trim21/actions/setup-go@master with: - path: | - ~/.cache/go-build - ~/go/pkg - key: go-cache-119-${{ hashFiles('**/go.sum') }}-build - restore-keys: | - go-cache-119-${{ hashFiles('**/go.sum') }}- - go-cache-119- + cache-namespace: build - name: Install Task - uses: arduino/setup-task@v1 + uses: arduino/setup-task@v2.0.0 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" - - run: go get ./... - - run: task build name: Build Binary diff --git a/.github/workflows/lint-review.yaml b/.github/workflows/lint-review.yaml index 73bb573b1..79a25d37e 100644 --- a/.github/workflows/lint-review.yaml +++ b/.github/workflows/lint-review.yaml @@ -1,57 +1,39 @@ -name: Lint Review +name: autofix.ci on: - push: - branches-ignore: - - renovate/** - pull_request_target: - types: [opened, synchronize, reopened] + pull_request: branches: - master +permissions: + contents: read + +env: + GOTOOLCHAIN: "local" + jobs: lint: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - - name: Install Node LTS - uses: actions/setup-node@v3 - - - name: Install Go - uses: actions/setup-go@v4 - with: - go-version: "1.20" - cache: false - - - run: npm i -g prettier - - name: Checkout code - uses: actions/checkout@v3 - if: ${{ github.event_name != 'push' }} + uses: actions/checkout@v6.0.2 with: - ref: "refs/pull/${{ github.event.number }}/head" + submodules: true - - run: "gh pr checkout ${{ github.event.number }}" - if: ${{ github.event_name != 'push' }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Install Node LTS + uses: actions/setup-node@v6.4.0 + with: + cache: yarn - - name: Checkout code - uses: actions/checkout@v3 - if: ${{ github.event_name == 'push' }} + - name: Install Go + uses: actions/setup-go@v6.4.0 + with: + go-version-file: "go.mod" - - name: prettier - run: prettier --write --list-different ./ + - run: yarn install --frozen-lockfile - - name: gofmt - run: gofmt -w -s . + - run: yarn run format - - run: git diff --exit-code + - run: gofmt -w -s . - - name: create review - if: ${{ failure() && github.event_name != 'push' }} - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add . - git commit -m 'style: ci auto format code' - git push + - uses: autofix-ci/action@v1.3.4 diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 8f089a884..7ebf56dde 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -2,8 +2,8 @@ name: Lint on: push: - branches-ignore: - - renovate/** + branches: + - master paths: - "**.go" - "**.go.json" @@ -24,32 +24,17 @@ on: jobs: lint: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v3 - - - name: Install Go - uses: actions/setup-go@v4 + - uses: actions/checkout@v6.0.2 with: - go-version-file: "go.mod" - cache: false + submodules: true - - name: Go Build Cache (lint) - uses: actions/cache@v3 + - uses: trim21/actions/setup-go@master with: - path: | - ~/.cache/go-build - ~/go/pkg - key: go-cache-119-${{ hashFiles('**/go.sum') }}-lint - restore-keys: | - go-cache-119-${{ hashFiles('**/go.sum') }}- - go-cache-119- - - - run: go get -t ./... + cache-namespace: lint - name: Run linters - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v9.2.0 with: - version: v1.52.2 - skip-pkg-cache: true - skip-build-cache: true + version: v2.11.4 diff --git a/.github/workflows/release-docker.yaml b/.github/workflows/release-docker.yaml index 0d31385ae..8643f36bc 100644 --- a/.github/workflows/release-docker.yaml +++ b/.github/workflows/release-docker.yaml @@ -4,70 +4,71 @@ on: push: tags: - "v*.*.*" - branches: - - master + branches-ignore: + - "renovate/**" + +permissions: + packages: write + jobs: docker: name: "docker" - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 env: IMAGE: "ghcr.io/${{ github.repository_owner }}/chii" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6.0.2 with: - fetch-depth: 0 + submodules: true - - name: Install Go - uses: actions/setup-go@v4 + - uses: trim21/actions/setup-go@master with: - go-version-file: "go.mod" - cache: false + cache-namespace: build - - name: Go Build Cache (build) - uses: actions/cache@v3 - with: - path: | - ~/.cache/go-build - ~/go/pkg - key: go-cache-119-${{ hashFiles('**/go.sum') }}-build - restore-keys: | - go-cache-119-${{ hashFiles('**/go.sum') }}- - go-cache-119- + - run: echo "SHA=${GITHUB_REF##*/}" >> $GITHUB_ENV + if: "${{ startsWith(github.ref, 'refs/tags/') }}" - - run: | - echo "TAG=master-$(git describe --match='v*' --tags)" >> $GITHUB_ENV - echo "REF=master" >> $GITHUB_ENV + - run: echo "SHA=$(git show --no-patch --no-notes --date=short-local --pretty='%as-%h')" >> $GITHUB_ENV if: "${{ !startsWith(github.ref, 'refs/tags/') }}" - - - run: | - echo "TAG=${GITHUB_REF##*/}" >> $GITHUB_ENV - echo "REF=latest" >> $GITHUB_ENV - if: "${{ startsWith(github.ref, 'refs/tags/') }}" + env: + TZ: UTC - run: node .github/scripts/write-version.js - name: Install Task - uses: arduino/setup-task@v1 + uses: arduino/setup-task@v2.0.0 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" - run: task build name: Build Binary - - run: docker build -t current -f etc/Dockerfile . + - name: Docker metadata + id: meta + uses: docker/metadata-action@v6.0.0 + with: + images: ${{ env.IMAGE }} + tags: | + type=semver,event=tag,pattern=v{{version}} + type=raw,value={{commit_date 'YYYY-MM-DD'}}-{{sha}} + + type=ref,event=branch + type=ref,event=branch,suffix=-${{ env.SHA }} - - uses: docker/login-action@v2 + - uses: docker/login-action@v4.1.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ github.token }} - - name: push tags - run: | - docker tag current "${IMAGE}:${REF}" - docker push "${IMAGE}:${REF}" - - docker tag current "${IMAGE}:${TAG}" - docker push "${IMAGE}:${TAG}" + - name: Build Final Docker Image + uses: docker/build-push-action@v7.1.0 + with: + context: ./ + provenance: false + file: etc/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/release-github.yaml b/.github/workflows/release-github.yaml new file mode 100644 index 000000000..b0b8e3da6 --- /dev/null +++ b/.github/workflows/release-github.yaml @@ -0,0 +1,32 @@ +name: Release + +on: + push: + tags: + - "v*.*.*" + +env: + GOTOOLCHAIN: "local" + +jobs: + github: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6.0.2 + with: + fetch-depth: 0 + + - name: Generate Changelog + id: changelog + uses: requarks/changelog-action@v1.10.3 + with: + token: ${{ github.token }} + tag: ${{ github.ref_name }} + writeToFile: false + restrictToTypes: feat,fix,revert + + - name: Upload GitHub Release + run: gh release create "${GITHUB_REF}" --notes "${CHANGELOG}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CHANGELOG: "${{ steps.changelog.outputs.changes }}" diff --git a/.github/workflows/release-openapi.yaml b/.github/workflows/release-openapi.yaml index 907277218..9a3f0a37b 100644 --- a/.github/workflows/release-openapi.yaml +++ b/.github/workflows/release-openapi.yaml @@ -2,23 +2,26 @@ name: Release(openapi) on: push: - tags: - - "v*.*.*" + branches: + - master workflow_dispatch: +env: + GOTOOLCHAIN: "local" + jobs: openapi: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v6.0.2 + - uses: actions/setup-node@v6.4.0 with: - node-version: 16 + node-version: 20 - - run: npm ci - - run: npm run build + - run: yarn install --frozen-lockfile + - run: yarn run build - - uses: actions/checkout@v3 + - uses: actions/checkout@v6.0.2 with: repository: "bangumi/api" path: api @@ -26,7 +29,7 @@ jobs: - run: cp ./dist/v0.yaml ./api/open-api/v0.yaml - name: Create Pull Request - uses: peter-evans/create-pull-request@v5 + uses: peter-evans/create-pull-request@v8.1.1 with: path: api token: ${{ secrets.PAT }} @@ -34,18 +37,3 @@ jobs: push-to-fork: Trim21-bot/api branch: "update-upstream" author: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" - - - uses: actions/checkout@v3 - with: - repository: "bangumi/dev-docs" - path: dev-docs -# - run: cp ./dist/private.yaml ./dev-docs/api.yaml -# - name: Create Pull Request -# uses: peter-evans/create-pull-request@v4 -# with: -# path: dev-docs -# token: ${{ secrets.PAT }} -# title: Update Openapi Specification from bangumi/server -# push-to-fork: Trim21-bot/dev-docs -# branch: "update-upstream" -# author: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index 2efafffc1..000000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,29 +0,0 @@ -name: Release - -on: - push: - tags: - - "v*.*.*" - -jobs: - github: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - uses: actions/setup-node@v3 - with: - node-version: 16 - - - run: npm ci - - - run: node scripts/changelog.js > changelog.md - - - run: echo "TAG=${GITHUB_REF##*/}" >> $GITHUB_ENV - - - name: Upload GitHub Release - run: gh release create "$TAG" --title "$TAG" -F changelog.md - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/security.yaml b/.github/workflows/security.yaml new file mode 100644 index 000000000..71eb4559c --- /dev/null +++ b/.github/workflows/security.yaml @@ -0,0 +1,40 @@ +name: Security Vulnerability Check + +on: + push: + branches: + - master + paths: + - "**.go" + - "go.mod" + - "go.sum" + - ".golangci.yaml" + - ".github/workflows/security.yaml" + schedule: + - cron: "15 3 * * 4" + +jobs: + vulnerability-scan: + runs-on: ubuntu-24.04 + permissions: + issues: write + steps: + - uses: actions/checkout@v6.0.2 + with: + submodules: true + + - uses: trim21/actions/setup-go@master + with: + cache-namespace: sec + + - run: go install golang.org/x/vuln/cmd/govulncheck@latest + + - run: govulncheck ./... + + - name: Notify failed build + uses: jayqi/failed-build-issue-action@v1.2.0 + if: failure() + with: + github-token: ${{ github.token }} + always-create-new-issue: false + label-name: vulncheck diff --git a/.github/workflows/test-openapi.yaml b/.github/workflows/test-openapi.yaml index 0e9df4c6d..656142471 100644 --- a/.github/workflows/test-openapi.yaml +++ b/.github/workflows/test-openapi.yaml @@ -2,8 +2,8 @@ name: Test(openapi) on: push: - branches-ignore: - - renovate/** + branches: + - master paths: - "openapi/**" - "package.json" @@ -18,15 +18,21 @@ on: - "package-lock.json" - ".github/workflows/test-openapi.yaml" +env: + GOTOOLCHAIN: "local" + jobs: test: - runs-on: ubuntu-22.04 + runs-on: "${{ matrix.os }}" + strategy: + matrix: + os: [ubuntu-latest, windows-latest] steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v6.0.2 + - uses: actions/setup-node@v6.4.0 with: node-version: "lts/*" - cache: "npm" + cache: "yarn" - - run: npm ci - - run: npm test + - run: yarn install --frozen-lockfile + - run: yarn run test diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 883c54ce3..87c5ed1cb 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -2,8 +2,8 @@ name: Test on: push: - branches-ignore: - - renovate/** + branches: + - master paths: - ".github/workflows/test.yaml" - "go.mod" @@ -24,45 +24,44 @@ on: jobs: test: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - - run: git clone https://github.com/bangumi/dev-env $HOME/dev-env - - run: cd ~/dev-env && docker-compose up -d + - run: git clone https://github.com/bangumi/dev-env $HOME/dev-env --branch=gh-pages + - run: cd ~/dev-env && docker compose up -d - - uses: actions/checkout@v3 + - uses: actions/checkout@v6.0.2 with: submodules: recursive - - name: Install Go - uses: actions/setup-go@v4 + - uses: trim21/actions/setup-go@master with: - go-version-file: "go.mod" - cache: false + cache-namespace: test - name: Install Task - uses: arduino/setup-task@v1 + uses: arduino/setup-task@v2.0.0 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" - - name: Go Build Cache (test) - uses: actions/cache@v3 + - name: Install gotestsum + uses: jaxxstorm/action-install-gh-release@v3.0.0 with: - path: | - ~/.cache/go-build - ~/go/pkg - key: go-cache-119-${{ hashFiles('**/go.sum') }}-test - restore-keys: | - go-cache-119-${{ hashFiles('**/go.sum') }}- - go-cache-119- + repo: gotestyourself/gotestsum + tag: v1.13.0 + platform: linux + arch: amd64 + env: + GITHUB_TOKEN: "${{ github.token }}" - run: go get -t ./... - run: bash $HOME/dev-env/wait_mysql_ready.sh - name: Run tests - run: task coverage + run: gotestsum --format-hide-empty-pkg -- -timeout 10s -tags test -race -coverpkg=./... -covermode=atomic -coverprofile=coverage.out ./... env: GORACE: halt_on_error=1 + TEST_MYSQL: "1" + TEST_REDIS: "1" MYSQL_HOST: 127.0.0.1 MYSQL_PORT: "3306" MYSQL_USER: user @@ -70,6 +69,7 @@ jobs: MYSQL_DB: bangumi REDIS_URI: "redis://:redis-pass@127.0.0.1:6379/0" - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v6.0.0 with: files: coverage.out + token: ${{ secrets.CODECOV_TOKEN }} # required diff --git a/.github/workflows/update-common.yaml b/.github/workflows/update-common.yaml new file mode 100644 index 000000000..d4f9d5a52 --- /dev/null +++ b/.github/workflows/update-common.yaml @@ -0,0 +1,41 @@ +name: update common defs + +on: + push: + branches: + - master + repository_dispatch: + types: + - ci-update-common + +permissions: + contents: write + +jobs: + update: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6.0.2 + with: + submodules: true + + - uses: actions/setup-node@v6.4.0 + with: + node-version: 22 + + - run: git reset --hard origin/master + working-directory: ./pkg/vars/common + + - run: yarn install --frozen-lockfile + - run: yarn run build-common + - run: yarn run format + - run: git add . + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v8.1.1 + with: + token: ${{ secrets.PAT }} + title: "feat: update common" + branch: "ci/update-common" + push-to-fork: "trim21-bot/bangumi-server" + author: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" diff --git a/.gitignore b/.gitignore index 9cf86daa0..4eb88f609 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,7 @@ tmp/ node_modules/ pnpm-lock.yaml -yarn.lock .task/ config.yaml +config.toml diff --git a/.gitmodules b/.gitmodules index 96e397aaf..3e704cf18 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,4 @@ -[submodule "pkg/wiki/testdata/wiki-syntax-spec"] - path = pkg/wiki/testdata/wiki-syntax-spec - url = https://github.com/bangumi/wiki-syntax-spec.git -[submodule "proto"] - path = proto - url = https://github.com/bangumi/proto.git +[submodule "pkg/vars/common"] + path = pkg/vars/common + url = https://github.com/bangumi/common + branch = master diff --git a/.golangci.yaml b/.golangci.yaml index 8cbb0107d..dc9266109 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,325 +1,255 @@ -# This file contains all available configuration options -# with their default values. - -# options for analysis running +version: "2" run: - # timeout for analysis, e.g. 30s, 5m, default is 1m - timeout: 5m - - # exit code when at least one issue was found, default is 1 + modules-download-mode: readonly issues-exit-code: 1 - - # include test files or not, default is true tests: true - - modules-download-mode: readonly - - # Allow multiple parallel golangci-lint instances running. - # If false (default) - golangci-lint acquires file lock on start. allow-parallel-runners: true - - skip-files: [] - - go: "1.18" - -# output configuration options output: - # colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions - # default is "colored-line-number" - format: colored-line-number - - # print lines of code with issue, default is true - print-issued-lines: true - - # print linter name in the end of issue text, default is true - print-linter-name: true - - # make issues output unique by line, default is true - uniq-by-line: true - - # add a prefix to the output file references; default is no prefix - path-prefix: "" - - # sorts results by: filepath, line and column - sort-results: true - -# all available settings of specific linters -linters-settings: - goheader: - template: |- - SPDX-License-Identifier: AGPL-3.0-only - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, version 3. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see - - errcheck: - # report about not checking of errors in type assertions: `a := b.(MyStruct)`; - # default is false: such cases aren't reported by default. - check-type-assertions: false - - # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; - # default is false: such cases aren't reported by default. - check-blank: false - exclude-functions: - - (*github.com/valyala/bytebufferpool.ByteBuffer).Write - - (*github.com/valyala/bytebufferpool.ByteBuffer).WriteByte - - (*github.com/valyala/bytebufferpool.ByteBuffer).WriteString - - errorlint: - # Check whether fmt.Errorf uses the %w verb for formatting errors. See the readme for caveats - errorf: true - # Check for plain type assertions and type switches - asserts: true - # Check for plain error comparisons - comparison: true - - forbidigo: - # Forbid the following identifiers (identifiers are written using regexp): - forbid: - - ^print.*$ - - ^fmt\.Println$ - - ^fmt\.Print$ - - gofmt: - simplify: true - rewrite-rules: - - pattern: "interface{}" - replacement: "any" - - funlen: - lines: 60 - statements: 40 - - gocyclo: - # minimal code complexity to report, 30 by default (but we recommend 10-20) - min-complexity: 15 - - godot: - # comments to be checked: `declarations`, `toplevel`, or `all` - scope: declarations - # list of regexps for excluding particular comment lines from check - exclude: [] - # example: exclude comments which contain numbers - # - '[0-9]+' - # check that each sentence starts with a capital letter - capital: false - - gci: - sections: - - standard # Captures all standard packages if they do not match another section. - - default # Contains all imports that could not be matched to another section type. - - prefix(github.com/bangumi/server) # Groups all imports with the specified Prefix. - sectionSeparators: - - newLine - - depguard: - list-type: denylist - # Check the list against standard lib. - # Default: false - include-go-root: true - # A list of packages for the list type specified. - # Default: [] - packages: - - "github.com/sirupsen/logrus" - - "github.com/golang/mock/gomock" - # A list of packages for the list type specified. - # Specify an error message to output when a denied package is used. - # Default: [] - packages-with-error-message: - - "github.com/goccy/go-json": 'use "github.com/bytedance/sonic"' - - "github.com/sirupsen/logrus": 'use "app/pkg/logger"' - - "github.com/golang/mock": 'use "github.com/stretchr/testify/mock" and "github.com/vektra/mockery"' - # Create additional guards that follow the same configuration pattern. - # Results from all guards are aggregated together. - additional-guards: - - list-type: denylist - include-go-root: false - packages: - - github.com/stretchr/testify - # Specify rules by which the linter ignores certain files for consideration. - ignore-file-rules: - - "!**/*_test.go" - - "!/mocks/**/*.go" - - gomnd: - settings: - mnd: - # the list of enabled checks, see https://github.com/tommy-muehle/go-mnd/#checks for description. - checks: - - argument - - case - - operation - - return - - assign - ignored-functions: strconv\..*,time\..*,make,math\..*,strings\..* - ignored-numbers: 1,2,3,10,100,1000,10000 - - gosimple: - # Select the Go version to target. The default is '1.13'. - go: "1.18" - # https://staticcheck.io/docs/options#checks - checks: ["all"] - - importas: - no-unaliased: false - no-extra-aliases: false - alias: - - pkg: "log" - alias: stdLog - - pkg: "gorm.io/gorm/logger" - alias: gormLogger - - pkg: "github.com/go-playground/universal-translator" - alias: ut - - pkg: "github.com/go-playground/validator/v10/translations/zh" - alias: zhTranslations - - lll: - # max line length, lines longer will be reported. Default is 120. - # '\t' is counted as 1 character by default, and can be changed with the tab-width option - line-length: 120 - # tab width in spaces. Default to 1. - tab-width: 2 - - misspell: - # Correct spellings using locale preferences for US or UK. - # Default is to use a neutral variety of English. - # Setting locale to US will correct the British spelling of 'colour' to 'color'. - locale: US - - staticcheck: - # Select the Go version to target. The default is '1.13'. - go: "1.18" - # https://staticcheck.io/docs/options#checks - checks: ["all"] - - stylecheck: - # Select the Go version to target. The default is '1.13'. - go: "1.18" - - testpackage: - # regexp pattern to skip files - skip-regexp: (export|internal)_test\.go - - unused: - # Select the Go version to target. The default is '1.13'. - go: "1.18" - - exhaustive: - # check switch statements in generated files also - check-generated: false - # indicates that switch statements are to be considered exhaustive if a - # 'default' case is present, even if all enum members aren't listed in the - # switch - default-signifies-exhaustive: true - govet: - enable-all: true - disable: - - fieldalignment - # - shadow - - nlreturn: - block-size: 3 - - ifshort: - # Maximum length of vars declaration measured in number of lines, after which linter won't suggest using short syntax. - # Has higher priority than max-decl-chars. - max-decl-lines: 1 - # Maximum length of vars declaration measured in number of characters, after which linter won't suggest using short syntax. - max-decl-chars: 30 - - tagliatelle: - # Check the struck tag name case. - case: - # Use the struct field name to check the name of the struct tag. - # Default: false - use-field-name: false - rules: - # Any struct tag type can be used. - # Support string case: `camel`, `pascal`, `kebab`, `snake`, `goCamel`, `goPascal`, `goKebab`, `goSnake`, `upper`, `lower` - json: snake - yaml: snake - + formats: + text: + path: stdout linters: - enable-all: true - disable: - - nolintlint - - ireturn - - contextcheck - - bodyclose - - wsl # noisy - - varnamelen # noisy - - exhaustivestruct # too noisy, default zero value is a good thing. - - exhaustruct # too noisy - - gofumpt # break import grouping - - prealloc # perf - - maligned # replaced by go vert - - dupl - - cyclop # we have gocyclo - - goimports # we have gci already - - nlreturn - - godox - - execinquery - - nonamedreturns - # useless - - sqlclosecheck - - rowserrcheck - - nilerr - - dupword - - wrapcheck - # 1.18 - - wastedassign - - nosnakecase # false positive for variable and const defined in other lib - - structcheck - # deprecated - - varcheck - - deadcode - - interfacer - - ifshort - - scopelint - - golint - - fast: false + default: none + enable: + - asasalint + - asciicheck + - bidichk + - containedctx + - copyloopvar + - depguard + - dogsled + - durationcheck + - errcheck + - errchkjson + - errname + - errorlint + - exhaustive + - forbidigo + - forcetypeassert + - funlen + - gocheckcompilerdirectives + - gochecknoglobals + - gochecknoinits + # - gocognit + - goconst + - gocritic + # - gocyclo + - godot + - gomoddirectives + - gomodguard + - goprintffuncname + - gosec + - govet + - grouper + - importas + - ineffassign + - interfacebloat + - lll + - loggercheck + - maintidx + - makezero + - misspell + - mnd + - nakedret + - nestif + - nilnil + - noctx + - paralleltest + - predeclared + - promlinter + - reassign + - revive + - staticcheck + - tagliatelle + - testableexamples + - testpackage + - thelper + - tparallel + - unconvert + - unparam + - unused + - usestdlibvars + - whitespace + settings: + depguard: + rules: + main: + files: + - $all + - "!$test" + - "!**/internal/pkg/test/**.go" + deny: + - pkg: github.com/sirupsen/logrus + desc: use "app/pkg/logger" + - pkg: github.com/golang/mock + desc: use "github.com/stretchr/testify/mock" and "github.com/vektra/mockery" + - pkg: github.com/stretchr/testify + desc: test assert package not allowed + test: + files: + - $test + - "**/internal/pkg/test/**.go" + deny: + - pkg: github.com/golang/mock + desc: use "github.com/stretchr/testify/mock" and "github.com/vektra/mockery" + errcheck: + check-type-assertions: false + check-blank: false + exclude-functions: + - (*github.com/valyala/bytebufferpool.ByteBuffer).Write + - (*github.com/valyala/bytebufferpool.ByteBuffer).WriteByte + - (*github.com/valyala/bytebufferpool.ByteBuffer).WriteString + errorlint: + errorf: true + asserts: true + comparison: true + exhaustive: + default-signifies-exhaustive: true + forbidigo: + forbid: + - pattern: ^print.*$ + - pattern: ^fmt\.Println$ + - pattern: ^fmt\.Print$ + funlen: + lines: 60 + statements: 40 + gocyclo: + min-complexity: 15 + godot: + scope: declarations + capital: false + gosec: + excludes: + - G115 + govet: + disable: + - fieldalignment + enable-all: true + importas: + alias: + - pkg: log + alias: stdLog + - pkg: gorm.io/gorm/logger + alias: gormLogger + - pkg: github.com/go-playground/universal-translator + alias: ut + - pkg: github.com/go-playground/validator/v10/translations/zh + alias: zhTranslations + no-unaliased: false + no-extra-aliases: false + lll: + line-length: 120 + tab-width: 2 + misspell: + locale: US + mnd: + checks: + - argument + - case + - operation + - return + - assign + ignored-numbers: + - "1" + - "2" + - "3" + - "10" + - "24" + - "100" + - "1000" + - "10000" + ignored-functions: + - strconv\..* + - time\..* + - make + - math\..* + - strings\..* + nlreturn: + block-size: 3 + revive: + rules: + - name: unused-parameter + disabled: true + staticcheck: + checks: + - all + tagliatelle: + case: + rules: + json: snake + yaml: snake + use-field-name: false + testpackage: + skip-regexp: (export|internal)_test\.go + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - gochecknoglobals + - gochecknoinits + - wrapcheck + path: cmd/.* + - linters: + - funlen + - gochecknoglobals + - gocritic + - godot + - nosnakecase + path: .*_test\.go + - linters: + - gochecknoglobals + source: var .* = pool.New + - linters: + - gochecknoglobals + source: var Module = fx\.Module\( + - linters: + - gochecknoglobals + source: var .* = reflect\.TypeOf + - linters: + - gochecknoglobals + source: var .* sync\.Once + - linters: + - err113 + - errorlint + source: if err == redis.Nil { + - linters: + - paralleltest + text: Range statement for test \S+ does not use range value in test Run + - linters: + - nilerr + source: return false, nil + paths: + - third_party$ + - builtin$ + - examples$ issues: new: false fix: false - exclude-use-default: true - exclude-rules: - - path: "cmd/.*" - linters: [gochecknoglobals, gochecknoinits, wrapcheck] - - - path: '.*_test\.go' - linters: [gocritic, gochecknoglobals, godot, nosnakecase, funlen, musttag] - - - source: "var .* = pool.New" - linters: [gochecknoglobals] - - - source: 'var Module = fx\.Module\(' - linters: [gochecknoglobals] - - - source: 'var .* = reflect\.TypeOf' - linters: [gochecknoglobals] - - - source: 'var .* sync\.Once' - linters: [gochecknoglobals] - - - linters: [goerr113, errorlint] - source: "if err == redis.Nil {" - - # https://github.com/kunwardeep/paralleltest/issues/8 - - linters: - - paralleltest - text: "Range statement for test \\S+ does not use range value in test Run" - - linters: - - nilerr - source: "return false, nil" +formatters: + enable: + - gci + - gofmt + settings: + gci: + sections: + - standard + - default + - prefix(github.com/bangumi/server) + gofmt: + simplify: true + rewrite-rules: + - pattern: interface{} + replacement: any + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/.mockery.yaml b/.mockery.yaml index c49f758d3..39f3fa2d9 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -1 +1,63 @@ -with-expecter: True +all: false +dir: "./internal/mocks/" + +force-file-write: true + +log-level: info +pkgname: "mocks" +recursive: false +template: testify + +filename: "{{.SrcPackageName | firstUpper}}{{.InterfaceName}}.go" +structname: "{{.SrcPackageName | firstUpper}}{{.InterfaceName}}" + +packages: + github.com/bangumi/server/web/session: + interfaces: + Repo: + Manager: + github.com/bangumi/server/internal/pkg/cache: + config: + structname: "{{.InterfaceName}}" + interfaces: + RedisCache: + github.com/bangumi/server/internal/search: + interfaces: + Client: + github.com/bangumi/server/internal/timeline: + interfaces: + Service: + github.com/bangumi/server/internal/collections: + interfaces: + Repo: + github.com/bangumi/server/internal/person: + interfaces: + Repo: + Service: + github.com/bangumi/server/internal/episode: + interfaces: + Repo: + github.com/bangumi/server/internal/user: + interfaces: + Repo: + github.com/bangumi/server/internal/subject: + interfaces: + Repo: + CachedRepo: + + github.com/bangumi/server/internal/auth: + interfaces: + Repo: + Service: + github.com/bangumi/server/internal/tag: + interfaces: + Repo: + github.com/bangumi/server/internal/character: + interfaces: + Repo: + github.com/bangumi/server/internal/index: + interfaces: + Repo: + github.com/bangumi/server/internal/revision: + interfaces: + Repo: diff --git a/.prettierignore b/.prettierignore index 71703fa18..7d4c04f21 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,3 +3,5 @@ .vscode/ .idea/ static/ + +pkg/vars/common/ diff --git a/buf.gen.yaml b/buf.gen.yaml deleted file mode 100644 index 1a5232878..000000000 --- a/buf.gen.yaml +++ /dev/null @@ -1,9 +0,0 @@ -version: v1 - -plugins: - - plugin: go - out: generated/proto/go - opt: paths=source_relative - - plugin: go-grpc - out: generated/proto/go - opt: paths=source_relative diff --git a/buf.work.yaml b/buf.work.yaml deleted file mode 100644 index 1878b341b..000000000 --- a/buf.work.yaml +++ /dev/null @@ -1,3 +0,0 @@ -version: v1 -directories: - - proto diff --git a/canal/canal.go b/canal/canal.go index db51a2d5a..740230d01 100644 --- a/canal/canal.go +++ b/canal/canal.go @@ -18,24 +18,31 @@ import ( "context" "fmt" "net/http" + "os" + "os/signal" + "syscall" "time" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/trim21/errgo" "go.uber.org/fx" + "golang.org/x/sync/errgroup" "github.com/bangumi/server/config" "github.com/bangumi/server/dal" + "github.com/bangumi/server/internal/character" + "github.com/bangumi/server/internal/person" "github.com/bangumi/server/internal/pkg/cache" "github.com/bangumi/server/internal/pkg/driver" "github.com/bangumi/server/internal/pkg/logger" "github.com/bangumi/server/internal/pkg/sys" "github.com/bangumi/server/internal/search" "github.com/bangumi/server/internal/subject" + "github.com/bangumi/server/internal/tag" "github.com/bangumi/server/web/session" ) -const groupID = "my-group" +const groupID = "go-canal" var errNoTopic = fmt.Errorf("missing search events topic") @@ -46,20 +53,10 @@ func Main() error { return errgo.Trace(err) } - if len(cfg.Canal.Topics) == 0 { + if len(cfg.Kafka.Topics) == 0 { return errNoTopic } - var opt fx.Option - switch cfg.Canal.Broker { - case "redis": - opt = fx.Provide(newRedisStream) - case "kafka": - opt = fx.Provide(newKafkaStream) - default: - return fmt.Errorf("broker not supported, only support redis/kafka as debezium broker") // nolint: goerr113 - } - var h *eventHandler di := fx.New( fx.NopLogger, @@ -69,15 +66,18 @@ func Main() error { // driver and connector fx.Provide( - driver.NewMysqlConnectionPool, - driver.NewRedisClient, logger.Copy, cache.NewRedisCache, - subject.NewMysqlRepo, search.New, session.NewMysqlRepo, session.New, + driver.NewMysqlDriver, + driver.NewRueidisClient, logger.Copy, cache.NewRedisCache, + subject.NewMysqlRepo, character.NewMysqlRepo, person.NewMysqlRepo, + search.New, session.NewMysqlRepo, session.New, driver.NewS3, + tag.NewCachedRepo, + tag.NewMysqlRepo, newEventHandler, ), - opt, + fx.Provide(newKafkaStream), fx.Populate(&h), ) @@ -86,23 +86,31 @@ func Main() error { return errgo.Wrap(err, "fx") } - var errChan = make(chan error, 1) - // metrics http reporter mux := http.NewServeMux() mux.Handle("/metrics", promhttp.Handler()) srv := &http.Server{Addr: h.config.ListenAddr(), Handler: mux, ReadHeaderTimeout: time.Second} - go func() { errChan <- errgo.Wrap(srv.ListenAndServe(), "http") }() + + var wg errgroup.Group + defer srv.Shutdown(context.Background()) //nolint:errcheck + wg.Go(func() error { + return errgo.Wrap(srv.ListenAndServe(), "http") + }) - go func() { errChan <- errgo.Wrap(h.start(), "start") }() defer h.Close() + wg.Go(func() error { + return errgo.Wrap(h.start(), "start") + }) + + wg.Go(func() error { + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) - select { - case err := <-errChan: - return err - case <-sys.HandleSignal(): + s := <-sys.HandleSignal() logger.Info("receive signal, shutdown") - return nil - } + return fmt.Errorf("receive signal %s", s) + }) + + return wg.Wait() } diff --git a/canal/event.go b/canal/event.go index b20abed8b..9945ca323 100644 --- a/canal/event.go +++ b/canal/event.go @@ -19,8 +19,8 @@ import ( "encoding/json" "sync/atomic" - "github.com/minio/minio-go/v7" - "github.com/redis/go-redis/v9" + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/redis/rueidis" "github.com/trim21/errgo" "go.uber.org/zap" @@ -28,6 +28,7 @@ import ( "github.com/bangumi/server/internal/model" "github.com/bangumi/server/internal/pkg/logger/log" "github.com/bangumi/server/internal/search" + "github.com/bangumi/server/internal/tag" "github.com/bangumi/server/web/session" ) @@ -35,10 +36,11 @@ func newEventHandler( log *zap.Logger, appConfig config.AppConfig, session session.Manager, - redis *redis.Client, + redis rueidis.Client, stream Stream, + tag tag.Repo, search search.Client, - s3 *minio.Client, + s3 *s3.Client, ) *eventHandler { return &eventHandler{ redis: redis, @@ -48,6 +50,7 @@ func newEventHandler( s3: s3, stream: stream, log: log.Named("eventHandler"), + tag: tag, } } @@ -58,18 +61,19 @@ type eventHandler struct { log *zap.Logger search search.Client stream Stream - s3 *minio.Client // optional, check nil before use - redis *redis.Client + s3 *s3.Client // optional, check nil before use + redis rueidis.Client + tag tag.Repo } func (e *eventHandler) start() error { ee := e.stream.Read(context.Background(), func(msg Msg) error { - e.log.Debug("new message", zap.String("stream", msg.Stream), zap.String("id", msg.ID)) + e.log.Debug("new message", zap.String("topic", msg.Topic), zap.String("id", msg.ID)) err := e.onMessage(msg.Key, msg.Value) if err != nil { e.log.Error("failed to handle stream msg", - zap.Error(err), zap.String("stream", msg.Stream), zap.String("id", msg.ID)) + zap.Error(err), zap.String("stream", msg.Topic), zap.String("id", msg.ID)) return errgo.Trace(err) } @@ -85,10 +89,8 @@ func (e *eventHandler) Close() error { return nil } -func (e *eventHandler) OnUserPasswordChange(id model.UserID) error { - e.log.Info("user change password", log.User(id)) - - if err := e.session.RevokeUser(context.Background(), id); err != nil { +func (e *eventHandler) OnUserPasswordChange(ctx context.Context, id model.UserID) error { + if err := e.session.RevokeUser(ctx, id); err != nil { e.log.Error("failed to revoke user", log.User(id), zap.Error(err)) return errgo.Wrap(err, "session.RevokeUser") } @@ -103,26 +105,29 @@ func (e *eventHandler) onMessage(key, value []byte) error { return nil } - var k messageKey - if err := json.Unmarshal(key, &k); err != nil { + var p Payload + if err := json.Unmarshal(value, &p); err != nil { + e.log.Warn("failed to parse kafka value", zap.String("table", p.Source.Table), zap.Error(err)) return nil } - var v messageValue - if err := json.Unmarshal(value, &v); err != nil { - return nil - } + e.log.Debug("new message", zap.String("table", p.Source.Table)) - e.log.Debug("new message", zap.String("table", v.Payload.Source.Table)) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() var err error - switch v.Payload.Source.Table { + switch p.Source.Table { case "chii_subject_fields": - err = e.OnSubjectField(k.Payload, v.Payload) + err = e.OnSubjectField(ctx, key, p) case "chii_subjects": - err = e.OnSubject(k.Payload, v.Payload) + err = e.OnSubject(ctx, key, p) + case "chii_characters": + err = e.OnCharacter(ctx, key, p) + case "chii_persons": + err = e.OnPerson(ctx, key, p) case "chii_members": - err = e.OnUserChange(k.Payload, v.Payload) + err = e.OnUserChange(ctx, key, p) } return err @@ -138,15 +143,7 @@ const ( // https://debezium.io/documentation/reference/connectors/mysql.html // Table 9. Overview of change event basic content -type messageKey struct { - Payload json.RawMessage `json:"payload"` -} - -type messageValue struct { - Payload payload `json:"payload"` -} - -type payload struct { +type Payload struct { Before json.RawMessage `json:"before"` After json.RawMessage `json:"after"` Source source `json:"source"` diff --git a/canal/ifce.go b/canal/ifce.go index 1de12767c..892d546db 100644 --- a/canal/ifce.go +++ b/canal/ifce.go @@ -19,10 +19,10 @@ import ( ) type Msg struct { - ID string - Stream string - Key []byte - Value []byte + ID string + Topic string + Key []byte + Value []byte } type Stream interface { diff --git a/canal/on_character.go b/canal/on_character.go new file mode 100644 index 000000000..525d1b7d8 --- /dev/null +++ b/canal/on_character.go @@ -0,0 +1,45 @@ +package canal + +import ( + "context" + "encoding/json" + + "github.com/trim21/errgo" + "go.uber.org/zap" + + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/search" +) + +type CharacterKey struct { + ID model.CharacterID `json:"crt_id"` +} + +func (e *eventHandler) OnCharacter(ctx context.Context, key json.RawMessage, payload Payload) error { + var k CharacterKey + if err := json.Unmarshal(key, &k); err != nil { + return err + } + return e.onCharacterChange(ctx, k.ID, payload.Op) +} + +func (e *eventHandler) onCharacterChange(ctx context.Context, characterID model.CharacterID, op string) error { + switch op { + case opCreate: + if err := e.search.EventAdded(ctx, characterID, search.SearchTargetCharacter); err != nil { + return errgo.Wrap(err, "search.OnCharacterAdded") + } + case opUpdate, opSnapshot: + if err := e.search.EventUpdate(ctx, characterID, search.SearchTargetCharacter); err != nil { + return errgo.Wrap(err, "search.OnCharacterUpdate") + } + case opDelete: + if err := e.search.EventDelete(ctx, characterID, search.SearchTargetCharacter); err != nil { + return errgo.Wrap(err, "search.OnCharacterDelete") + } + default: + e.log.Warn("unexpected operator", zap.String("op", op)) + } + + return nil +} diff --git a/canal/on_person.go b/canal/on_person.go new file mode 100644 index 000000000..f5f75b4fa --- /dev/null +++ b/canal/on_person.go @@ -0,0 +1,44 @@ +package canal + +import ( + "context" + "encoding/json" + + "github.com/trim21/errgo" + "go.uber.org/zap" + + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/search" +) + +type PersonKey struct { + ID model.PersonID `json:"prsn_id"` +} + +func (e *eventHandler) OnPerson(ctx context.Context, key json.RawMessage, payload Payload) error { + var k PersonKey + if err := json.Unmarshal(key, &k); err != nil { + return err + } + return e.onPersonChange(ctx, k.ID, payload.Op) +} + +func (e *eventHandler) onPersonChange(ctx context.Context, personID model.PersonID, op string) error { + switch op { + case opCreate: + if err := e.search.EventAdded(ctx, personID, search.SearchTargetPerson); err != nil { + return errgo.Wrap(err, "search.OnPersonAdded") + } + case opUpdate, opSnapshot: + if err := e.search.EventUpdate(ctx, personID, search.SearchTargetPerson); err != nil { + return errgo.Wrap(err, "search.OnPersonUpdate") + } + case opDelete: + if err := e.search.EventDelete(ctx, personID, search.SearchTargetPerson); err != nil { + return errgo.Wrap(err, "search.OnPersonDelete") + } + default: + e.log.Warn("unexpected operator", zap.String("op", op)) + } + return nil +} diff --git a/canal/on_subject.go b/canal/on_subject.go index 214f9ddfa..5ecf0b406 100644 --- a/canal/on_subject.go +++ b/canal/on_subject.go @@ -19,47 +19,55 @@ import ( "encoding/json" "github.com/trim21/errgo" + "go.uber.org/zap" "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/search" ) -func (e *eventHandler) OnSubject(key json.RawMessage, payload payload) error { +type SubjectKey struct { + ID model.SubjectID `json:"subject_id"` +} + +type SubjectFieldKey struct { + ID model.SubjectID `json:"field_sid"` +} + +func (e *eventHandler) OnSubject(ctx context.Context, key json.RawMessage, payload Payload) error { var k SubjectKey if err := json.Unmarshal(key, &k); err != nil { - return nil + return err } - return e.onSubjectChange(k.ID, payload.Op) + return e.onSubjectChange(ctx, k.ID, payload.Op) } -func (e *eventHandler) OnSubjectField(key json.RawMessage, payload payload) error { +func (e *eventHandler) OnSubjectField(ctx context.Context, key json.RawMessage, payload Payload) error { var k SubjectFieldKey if err := json.Unmarshal(key, &k); err != nil { - return nil + return err } - return e.onSubjectChange(k.ID, payload.Op) + return e.onSubjectChange(ctx, k.ID, payload.Op) } -func (e *eventHandler) onSubjectChange(subjectID model.SubjectID, op string) error { +func (e *eventHandler) onSubjectChange(ctx context.Context, subjectID model.SubjectID, op string) error { switch op { - case opCreate, opUpdate, opSnapshot: - if err := e.search.OnSubjectUpdate(context.TODO(), subjectID); err != nil { + case opCreate: + if err := e.search.EventAdded(ctx, subjectID, search.SearchTargetSubject); err != nil { + return errgo.Wrap(err, "search.OnSubjectAdded") + } + case opUpdate, opSnapshot: + if err := e.search.EventUpdate(ctx, subjectID, search.SearchTargetSubject); err != nil { return errgo.Wrap(err, "search.OnSubjectUpdate") } case opDelete: - if err := e.search.OnSubjectDelete(context.TODO(), subjectID); err != nil { + if err := e.search.EventDelete(ctx, subjectID, search.SearchTargetSubject); err != nil { return errgo.Wrap(err, "search.OnSubjectDelete") } + default: + e.log.Warn("unexpected operator", zap.String("op", op)) } return nil } - -type SubjectKey struct { - ID model.SubjectID `json:"subject_id"` -} - -type SubjectFieldKey struct { - ID model.SubjectID `json:"field_sid"` -} diff --git a/canal/on_user.go b/canal/on_user.go index a4eeba523..8eae44a7e 100644 --- a/canal/on_user.go +++ b/canal/on_user.go @@ -20,8 +20,12 @@ import ( "encoding/json" "fmt" "strings" + "time" - "github.com/minio/minio-go/v7" + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/redis/rueidis" + "github.com/samber/lo" "github.com/trim21/errgo" "go.uber.org/zap" @@ -29,7 +33,7 @@ import ( "github.com/bangumi/server/internal/pkg/logger/log" ) -func (e *eventHandler) OnUserChange(key json.RawMessage, payload payload) error { +func (e *eventHandler) OnUserChange(ctx context.Context, key json.RawMessage, payload Payload) error { var k UserKey if err := json.Unmarshal(key, &k); err != nil { e.log.Error("failed to unmarshal json", zap.Error(err)) @@ -50,17 +54,19 @@ func (e *eventHandler) OnUserChange(key json.RawMessage, payload payload) error } if before.Password != after.Password { - err := e.OnUserPasswordChange(k.ID) + err := e.OnUserPasswordChange(ctx, k.ID) if err != nil { e.log.Error("failed to clear cache", zap.Error(err)) } } if before.NewNotify != after.NewNotify { - e.redis.Publish(context.Background(), fmt.Sprintf("event-user-notify-%d", k.ID), redisUserChannel{ - UserID: k.ID, - NewNotify: after.NewNotify, - }) + e.redis.Do(ctx, e.redis.B().Publish(). + Channel(fmt.Sprintf("event-user-notify-%d", k.ID)). + Message(rueidis.JSON(redisUserChannel{ + UserID: k.ID, + NewNotify: after.NewNotify, + })).Build()) } if before.Avatar != after.Avatar { @@ -69,14 +75,14 @@ func (e *eventHandler) OnUserChange(key json.RawMessage, payload payload) error } e.log.Debug("clear user avatar cache", log.User(k.ID)) - go e.clearImageCache(after.Avatar) + go e.clearImageCache(context.WithoutCancel(ctx), after.Avatar) } } return nil } -func (e *eventHandler) clearImageCache(avatar string) { +func (e *eventHandler) clearImageCache(ctx context.Context, avatar string) { p, q, ok := strings.Cut(avatar, "?") if !ok { p = avatar @@ -90,16 +96,37 @@ func (e *eventHandler) clearImageCache(avatar string) { e.log.Debug("clear image for prefix", zap.String("avatar", avatar), zap.String("prefix", p)) - files := e.s3.ListObjects(context.Background(), e.config.S3ImageResizeBucket, minio.ListObjectsOptions{ - Prefix: p, - Recursive: true, - }) + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() - for err := range e.s3.RemoveObjects( - context.Background(), e.config.S3ImageResizeBucket, files, - minio.RemoveObjectsOptions{}, - ) { - e.log.Error("failed to clear s3 cached image", zap.String("name", err.ObjectName), zap.Error(err.Err)) + pages := s3.NewListObjectsV2Paginator( + e.s3, + &s3.ListObjectsV2Input{Bucket: &e.config.S3ImageResizeBucket, Prefix: &p}, + ) + + for pages.HasMorePages() { + output, err := pages.NextPage(ctx) + if err != nil { + break + } + + if len(output.Contents) == 0 { + break + } + + _, err = e.s3.DeleteObjects(ctx, &s3.DeleteObjectsInput{ + Bucket: &e.config.S3ImageResizeBucket, + Delete: &types.Delete{ + Objects: lo.Map(output.Contents, func(item types.Object, index int) types.ObjectIdentifier { + return types.ObjectIdentifier{ + Key: item.Key, + } + }), + }, + }) + if err != nil { + e.log.Error("failed to clear s3 cached image", zap.Error(err)) + } } } diff --git a/canal/stream/redis_stream.go b/canal/stream/redis_stream.go deleted file mode 100644 index bc3198e06..000000000 --- a/canal/stream/redis_stream.go +++ /dev/null @@ -1,183 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package stream - -import ( - "context" - "errors" - "time" - - "github.com/redis/go-redis/v9" -) - -// A Message is a consumed message from a redis stream. -type Message struct { - Stream string - ID string - Values map[string]any -} - -type config struct { - group string - consumer string - streams []string // list of streams and ids, e.g. stream1 stream2 id1 id2 - count int64 - block time.Duration - noAck bool -} - -// An Option modifies the config. -type Option func(*config) - -// WithStream adds a stream to the consumer. -func WithStream(stream string) Option { - return func(cfg *config) { - cfg.streams = append(cfg.streams, stream) - } -} - -// WithCount sets the count for the config. -func WithCount(cnt int64) Option { - return func(cfg *config) { - cfg.count = cnt - } -} - -// WithBlock sets the block field of the config. -func WithBlock(duration time.Duration) Option { - return func(cfg *config) { - cfg.block = duration - } -} - -// WithNoAck sets the noAck field of the config. -func WithNoAck(noAck bool) Option { - return func(cfg *config) { - cfg.noAck = noAck - } -} - -// A Consumer consumes messages from a stream. -type Consumer struct { - client *redis.Client - cfg *config - lastIDs map[string]string -} - -// New creates a new consumer. -func New(client *redis.Client, group, consumer string, options ...Option) *Consumer { - cfg := &config{ - group: group, - consumer: consumer, - } - for _, opt := range options { - opt(cfg) - } - lastIDs := make(map[string]string) - for _, stream := range cfg.streams { - lastIDs[stream] = "0-0" - } - - return &Consumer{ - client: client, - cfg: cfg, - lastIDs: lastIDs, - } -} - -// Read reads messages from the stream. -func (c *Consumer) Read(ctx context.Context) ([]Message, error) { - for { - streams := make([]string, 0, len(c.cfg.streams)*2) - streams = append(streams, c.cfg.streams...) - for _, stream := range c.cfg.streams { - streams = append(streams, c.lastIDs[stream]) - } - - cmd := c.client.XReadGroup(ctx, &redis.XReadGroupArgs{ - Group: c.cfg.group, - Consumer: c.cfg.consumer, - Streams: streams, - Count: c.cfg.count, - Block: c.cfg.block, - NoAck: c.cfg.noAck, - }) - - vals, err := cmd.Result() - if err == redis.Nil { - if c.cfg.block >= 0 { - continue - } - - return nil, nil //nolint:revive - } else if err != nil { - return nil, err - } - - allLatest := true - for _, lastID := range c.lastIDs { - if lastID != ">" { - allLatest = false - } - } - - var msgs []Message - for _, stream := range vals { - if len(stream.Messages) == 0 { - c.lastIDs[stream.Stream] = ">" - } - for _, msg := range stream.Messages { - msgs = append(msgs, Message{ - Stream: stream.Stream, - ID: msg.ID, - Values: msg.Values, - }) - c.lastIDs[stream.Stream] = msg.ID - } - } - if len(msgs) > 0 || allLatest { - return msgs, nil - } - } -} - -// Ack acknowledges the messages. -func (c *Consumer) Ack(ctx context.Context, msgs ...Message) error { - if len(msgs) == 0 { - return nil - } - - if len(msgs) == 1 { - msg := msgs[0] - return errors.Join( - c.client.XAck(ctx, msg.Stream, c.cfg.group, msg.ID).Err(), - c.client.XDel(ctx, msg.Stream, msg.ID).Err(), - ) - } - - ids := map[string][]string{} - for _, msg := range msgs { - ids[msg.Stream] = append(ids[msg.Stream], msg.ID) - } - - _, err := c.client.Pipelined(ctx, func(p redis.Pipeliner) error { - for stream, msgIDs := range ids { - p.XAck(ctx, stream, c.cfg.group, msgIDs...) - p.XDel(ctx, stream, msgIDs...) - } - return nil - }) - return err -} diff --git a/canal/stream_kafka.go b/canal/stream_kafka.go index 5dbbd83b4..69629914f 100644 --- a/canal/stream_kafka.go +++ b/canal/stream_kafka.go @@ -22,7 +22,6 @@ import ( "sync/atomic" "github.com/segmentio/kafka-go" - "github.com/trim21/errgo" "go.uber.org/zap" "github.com/bangumi/server/config" @@ -33,9 +32,9 @@ import ( func newKafkaStream(cfg config.AppConfig) Stream { logger.Info("new kafka stream broker") k := kafka.NewReader(kafka.ReaderConfig{ - Brokers: []string{cfg.Canal.KafkaBroker}, + Brokers: []string{cfg.Kafka.Broker}, GroupID: groupID, - GroupTopics: cfg.Canal.Topics, + GroupTopics: cfg.Kafka.Topics, }) var ch = make(chan Msg, 1) @@ -74,18 +73,20 @@ func (s *kafkaStream) Read(ctx context.Context, onMessage func(msg Msg) error) e s.log.Debug("new message", zap.String("topic", msg.Topic)) m := Msg{ - ID: strconv.FormatInt(msg.Offset, 10), - Stream: msg.Topic, - Key: msg.Key, - Value: msg.Value, + ID: strconv.FormatInt(msg.Offset, 10), + Topic: msg.Topic, + Key: msg.Key, + Value: msg.Value, } if err := onMessage(m); err != nil { - return errgo.Trace(err) + s.log.Error("failed to process kafak message", zap.Error(err)) + continue } if err := s.k.CommitMessages(ctx, msg); err != nil { - return errgo.Trace(err) + s.log.Error("failed to commit kafak message", zap.Error(err)) + continue } } } diff --git a/canal/stream_redis.go b/canal/stream_redis.go deleted file mode 100644 index d5dd39ac8..000000000 --- a/canal/stream_redis.go +++ /dev/null @@ -1,143 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package canal - -import ( - "context" - "reflect" - "sync/atomic" - "time" - - "github.com/redis/go-redis/v9" - "github.com/samber/lo" - "github.com/trim21/errgo" - "go.uber.org/zap" - - "github.com/bangumi/server/canal/stream" - "github.com/bangumi/server/config" - "github.com/bangumi/server/internal/pkg/logger" -) - -func newRedisStream(cfg config.AppConfig, redisClient *redis.Client) (Stream, error) { - var ch = make(chan Msg, 1) - - reader := stream.New( - redisClient, - groupID, - "canal", - lo.Map(cfg.Canal.Topics, func(item string, _ int) stream.Option { - return stream.WithStream(item) - })..., - ) - - r := &redisStream{ - log: logger.Named("canal.search.stream"), - ch: ch, - cfg: cfg, - redis: redisClient, - reader: reader, - } - - for _, s := range r.cfg.Canal.Topics { - var infos, err = r.redis.XInfoGroups(context.Background(), s).Result() - if err != nil { - if err.Error() != "ERR no such key" { - return nil, errgo.Trace(err) - } - } - - groups := lo.SliceToMap(infos, func(item redis.XInfoGroup) (string, bool) { - return item.Name, true - }) - - if !groups[groupID] { - err := r.redis.XGroupCreateMkStream(context.Background(), s, groupID, "$").Err() - if err != nil { - return nil, errgo.Trace(err) - } - } - } - - return r, nil -} - -type redisStream struct { - log *zap.Logger - redis *redis.Client - ch chan Msg - cfg config.AppConfig - closed atomic.Bool - reader *stream.Consumer -} - -func (r *redisStream) Read(ctx context.Context, onMessage func(msg Msg) error) error { - for { - if r.closed.Load() { - return nil - } - - rr, err := r.reader.Read(ctx) - if err != nil { - r.log.Error("failed to read new messages", zap.Error(err)) - time.Sleep(time.Second) - continue - } - - for _, msg := range rr { - r.log.Debug("new message", zap.String("id", msg.ID), zap.String("s", msg.Stream)) - - rawKey := msg.Values["key"] - rawValue := msg.Values["value"] - - if rawKey == nil || rawValue == nil { - _ = r.reader.Ack(context.Background(), msg) - continue - } - - value, ok := rawValue.(string) - if !ok { - r.log.Error("failed to handle event", zap.String("id", msg.ID), - zap.String("value-type", reflect.TypeOf(rawKey).String())) - _ = r.reader.Ack(context.Background(), msg) - continue - } - - key, ok := rawKey.(string) - if !ok { - r.log.Error("failed to handle event", zap.String("id", msg.ID), - zap.String("key-type", reflect.TypeOf(rawKey).String())) - _ = r.reader.Ack(context.Background(), msg) - continue - } - - if err := onMessage(Msg{ID: msg.ID, Stream: msg.Stream, Key: []byte(key), Value: []byte(value)}); err != nil { - return errgo.Trace(err) - } - - if err := r.reader.Ack(ctx, msg); err != nil { - return errgo.Trace(err) - } - } - } -} - -func (r *redisStream) Close() error { - r.closed.Store(true) - return nil -} - -func (r *redisStream) Ack(ctx context.Context, msg Msg) error { - return r.reader.Ack(ctx, stream.Message{Stream: msg.Stream, ID: msg.ID}) -} diff --git a/cmd/archive/main.go b/cmd/archive/main.go index 955c2f655..6d166b562 100644 --- a/cmd/archive/main.go +++ b/cmd/archive/main.go @@ -21,10 +21,15 @@ import ( "encoding/json" "fmt" "io" + "math" "os" "path/filepath" + "sort" + "strings" + "unicode/utf8" "github.com/go-sql-driver/mysql" + "github.com/samber/lo" "github.com/spf13/cobra" "github.com/trim21/errgo" "go.uber.org/fx" @@ -38,6 +43,7 @@ import ( "github.com/bangumi/server/internal/model" "github.com/bangumi/server/internal/pkg/driver" "github.com/bangumi/server/internal/pkg/logger" + subjectDto "github.com/bangumi/server/internal/subject" ) const defaultStep = 50 @@ -75,7 +81,7 @@ func start(out string) { err := fx.New( fx.NopLogger, fx.Provide( - driver.NewMysqlConnectionPool, dal.NewDB, + driver.NewMysqlDriver, dal.NewGormDB, config.NewAppConfig, logger.Copy, @@ -130,6 +136,7 @@ func start(out string) { {FileName: "subject-persons.jsonlines", Fn: exportSubjectPersonRelations}, {FileName: "subject-characters.jsonlines", Fn: exportSubjectCharacterRelations}, {FileName: "person-characters.jsonlines", Fn: exportPersonCharacterRelations}, + {FileName: "person-relations.jsonlines", Fn: exportPersonRelations}, } { w, err := z.Create(s.FileName) if err != nil { @@ -162,6 +169,27 @@ func getMaxID(q *query.Query) { maxPersonID = lastPerson.ID } +type Score struct { + Field1 uint32 `json:"1"` + Field2 uint32 `json:"2"` + Field3 uint32 `json:"3"` + Field4 uint32 `json:"4"` + Field5 uint32 `json:"5"` + Field6 uint32 `json:"6"` + Field7 uint32 `json:"7"` + Field8 uint32 `json:"8"` + Field9 uint32 `json:"9"` + Field10 uint32 `json:"10"` +} + +type Favorite struct { + Wish uint32 `json:"wish"` + Done uint32 `json:"done"` + Doing uint32 `json:"doing"` + OnHold uint32 `json:"on_hold"` + Dropped uint32 `json:"dropped"` +} + type Subject struct { ID model.SubjectID `json:"id"` Type model.SubjectType `json:"type"` @@ -171,38 +199,134 @@ type Subject struct { Platform uint16 `json:"platform"` Summary string `json:"summary"` Nsfw bool `json:"nsfw"` + + Tags []Tag `json:"tags"` + MetaTags []string `json:"meta_tags"` + Score float64 `json:"score"` + ScoreDetails Score `json:"score_details"` + Rank uint32 `json:"rank"` + Date string `json:"date"` + Favorite Favorite `json:"favorite"` + + Series bool `json:"series"` +} + +type Tag struct { + Name string `json:"name"` + Count uint `json:"count"` } func exportSubjects(q *query.Query, w io.Writer) { for i := model.SubjectID(0); i < maxSubjectID; i += defaultStep { - subjects, err := q.WithContext(ctx).Subject. + subjects, err := q.WithContext(ctx).Subject.Preload(q.Subject.Fields). Where(q.Subject.ID.Gt(i), q.Subject.ID.Lte(i+defaultStep), q.Subject.Ban.Eq(0)).Find() if err != nil { panic(err) } for _, subject := range subjects { - encode(w, Subject{ - ID: subject.ID, - Type: subject.TypeID, - Name: subject.Name, - NameCN: subject.NameCN, - Infobox: subject.Infobox, - Platform: subject.Platform, - Summary: subject.Summary, - Nsfw: subject.Nsfw, + tags, err := subjectDto.ParseTags(subject.Fields.Tags) + if err != nil { + tags = []model.Tag{} + } + + sort.Slice(tags, func(i, j int) bool { + return tags[i].Count >= tags[j].Count + }) + + tags = lo.Filter(lo.Slice(tags, 0, 11), func(item model.Tag, index int) bool { //nolint:mnd + return utf8.RuneCountInString(item.Name) < 10 || item.Count >= 10 + }) + + encodedTags := lo.Map(tags, func(item model.Tag, index int) Tag { + return Tag{ + Name: item.Name, + Count: item.Count, + } }) + + f := subject.Fields + var total = f.Rate1 + f.Rate2 + f.Rate3 + f.Rate4 + f.Rate5 + f.Rate6 + f.Rate7 + f.Rate8 + f.Rate9 + f.Rate10 + var score float64 + if total != 0 { + score = float64(1*f.Rate1+2*f.Rate2+3*f.Rate3+4*f.Rate4+5*f.Rate5+ + 6*f.Rate6+7*f.Rate7+8*f.Rate8+9*f.Rate9+10*f.Rate10) / float64(total) + } + + encodedDate := "" + if !subject.Fields.Date.IsZero() { + encodedDate = subject.Fields.Date.Format("2006-01-02") + } + + var metaTags = []string{} + + for _, v := range strings.Split(subject.FieldMetaTags, " ") { + v = strings.TrimSpace(v) + if v == "" { + continue + } + + metaTags = append(metaTags, v) + } + + encode(w, buildSubjectArchiveRecord(subject, encodedTags, metaTags, encodedDate, score)) } } } +func buildSubjectArchiveRecord( + subject *dao.Subject, + tags []Tag, + metaTags []string, + encodedDate string, + score float64, +) Subject { + return Subject{ + ID: subject.ID, + Type: subject.TypeID, + Name: string(subject.Name), + NameCN: string(subject.NameCN), + Infobox: string(subject.Infobox), + Platform: subject.Platform, + Summary: subject.Summary, + Nsfw: subject.Nsfw, + Rank: subject.Fields.Rank, + Tags: tags, + MetaTags: metaTags, + Score: math.Round(score*10) / 10, + ScoreDetails: Score{ + Field1: subject.Fields.Rate1, + Field2: subject.Fields.Rate2, + Field3: subject.Fields.Rate3, + Field4: subject.Fields.Rate4, + Field5: subject.Fields.Rate5, + Field6: subject.Fields.Rate6, + Field7: subject.Fields.Rate7, + Field8: subject.Fields.Rate8, + Field9: subject.Fields.Rate9, + Field10: subject.Fields.Rate10, + }, + Date: encodedDate, + Series: subject.Series, + Favorite: Favorite{ + Wish: subject.Wish, + Done: subject.Done, + Doing: subject.Doing, + OnHold: subject.OnHold, + Dropped: subject.Dropped, + }, + } +} + type Person struct { - ID model.PersonID `json:"id"` - Name string `json:"name"` - Type uint8 `json:"type"` - Career []string `json:"career"` - Infobox string `json:"infobox"` - Summary string `json:"summary"` + ID model.PersonID `json:"id"` + Name string `json:"name"` + Type uint8 `json:"type"` + Career []string `json:"career"` + Infobox string `json:"infobox"` + Summary string `json:"summary"` + Comments uint32 `json:"comments"` + Collects uint32 `json:"collects"` } func exportPersons(q *query.Query, w io.Writer) { @@ -214,18 +338,24 @@ func exportPersons(q *query.Query, w io.Writer) { } for _, p := range persons { - encode(w, Person{ - ID: p.ID, - Name: p.Name, - Type: p.Type, - Career: careers(p), - Infobox: p.Infobox, - Summary: p.Summary, - }) + encode(w, buildPersonArchiveRecord(p)) } } } +func buildPersonArchiveRecord(p *dao.Person) Person { + return Person{ + ID: p.ID, + Name: string(p.Name), + Type: p.Type, + Career: careers(p), + Infobox: string(p.Infobox), + Summary: p.Summary, + Comments: p.Comment, + Collects: p.Collects, + } +} + func careers(p *dao.Person) []string { s := make([]string, 0, 7) @@ -249,10 +379,6 @@ func careers(p *dao.Person) []string { s = append(s, "seiyu") } - if p.Writer { - s = append(s, "writer") - } - if p.Illustrator { s = append(s, "illustrator") } @@ -265,11 +391,13 @@ func careers(p *dao.Person) []string { } type Character struct { - ID model.CharacterID `json:"id"` - Role uint8 `json:"role"` - Name string `json:"name"` - Infobox string `json:"infobox"` - Summary string `json:"summary"` + ID model.CharacterID `json:"id"` + Role uint8 `json:"role"` + Name string `json:"name"` + Infobox string `json:"infobox"` + Summary string `json:"summary"` + Comments uint32 `json:"comments"` + Collects uint32 `json:"collects"` } func exportCharacters(q *query.Query, w io.Writer) { @@ -281,17 +409,23 @@ func exportCharacters(q *query.Query, w io.Writer) { } for _, c := range characters { - encode(w, Character{ - ID: c.ID, - Name: c.Name, - Role: c.Role, - Infobox: c.Infobox, - Summary: c.Summary, - }) + encode(w, buildCharacterArchiveRecord(c)) } } } +func buildCharacterArchiveRecord(c *dao.Character) Character { + return Character{ + ID: c.ID, + Name: string(c.Name), + Role: c.Role, + Infobox: string(c.Infobox), + Summary: c.Summary, + Comments: c.Comment, + Collects: c.Collects, + } +} + type Episode struct { ID model.EpisodeID `json:"id"` Name string `json:"name"` @@ -299,6 +433,7 @@ type Episode struct { Description string `json:"description"` AirDate string `json:"airdate"` Disc uint8 `json:"disc"` + Duration string `json:"duration"` SubjectID model.SubjectID `json:"subject_id"` Sort float32 `json:"sort"` Type episode.Type `json:"type"` @@ -323,6 +458,7 @@ func exportEpisodes(q *query.Query, w io.Writer) { NameCn: e.NameCn, Sort: e.Sort, SubjectID: e.SubjectID, + Duration: e.Duration, Description: e.Desc, Type: e.Type, AirDate: e.Airdate, @@ -336,7 +472,7 @@ type SubjectRelation struct { SubjectID model.SubjectID `json:"subject_id"` RelationType uint16 `json:"relation_type"` RelatedSubjectID model.SubjectID `json:"related_subject_id"` - Order uint8 `json:"order"` + Order uint16 `json:"order"` } func exportSubjectRelations(q *query.Query, w io.Writer) { @@ -363,6 +499,7 @@ type SubjectPerson struct { PersonID model.PersonID `json:"person_id"` SubjectID model.SubjectID `json:"subject_id"` Position uint16 `json:"position"` + AppearEps string `json:"appear_eps"` } func exportSubjectPersonRelations(q *query.Query, w io.Writer) { @@ -379,6 +516,7 @@ func exportSubjectPersonRelations(q *query.Query, w io.Writer) { PersonID: rel.PersonID, SubjectID: rel.SubjectID, Position: rel.PrsnPosition, + AppearEps: rel.PrsnAppearEps, }) } } @@ -388,7 +526,7 @@ type SubjectCharacter struct { CharacterID model.CharacterID `json:"character_id"` SubjectID model.SubjectID `json:"subject_id"` Type uint8 `json:"type"` - Order uint8 `json:"order"` + Order uint16 `json:"order"` } func exportSubjectCharacterRelations(q *query.Query, w io.Writer) { @@ -415,6 +553,7 @@ type PersonCharacter struct { PersonID model.PersonID `json:"person_id"` SubjectID model.SubjectID `json:"subject_id"` CharacterID model.CharacterID `json:"character_id"` + Type uint8 `json:"type"` Summary string `json:"summary"` } @@ -432,12 +571,44 @@ func exportPersonCharacterRelations(q *query.Query, w io.Writer) { PersonID: rel.PersonID, SubjectID: rel.SubjectID, CharacterID: rel.CharacterID, + Type: rel.RltType, Summary: rel.Summary, }) } } } +type PersonRelation struct { + PersonType string `json:"person_type"` + PersonID model.PersonID `json:"person_id"` + RelatedPersonID model.PersonID `json:"related_person_id"` + RelationType uint32 `json:"relation_type"` + Spoiler bool `json:"spoiler"` + Ended bool `json:"ended"` +} + +func exportPersonRelations(q *query.Query, w io.Writer) { + for i := model.PersonID(0); i < maxPersonID; i += defaultStep { + relations, err := q.WithContext(context.Background()).PersonRelation. + Order(q.PersonRelation.PersonID, q.PersonRelation.PersonID). + Where(q.PersonRelation.PersonID.Gt(i), q.PersonRelation.PersonID.Lte(i+defaultStep)).Find() + if err != nil { + panic(err) + } + + for _, rel := range relations { + encode(w, PersonRelation{ + PersonType: rel.PersonType, + PersonID: rel.PersonID, + RelatedPersonID: rel.RelatedPersonID, + RelationType: rel.RelationType, + Spoiler: rel.Spoiler, + Ended: rel.Ended, + }) + } + } +} + func encode(w io.Writer, object any) { if err := json.NewEncoder(w).Encode(object); err != nil { panic(err) diff --git a/cmd/archive/main_test.go b/cmd/archive/main_test.go new file mode 100644 index 000000000..d50a8334c --- /dev/null +++ b/cmd/archive/main_test.go @@ -0,0 +1,78 @@ +//nolint:testpackage +package archive + +import ( + "bytes" + "encoding/json" + "html" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/bangumi/server/dal/dao" + "github.com/bangumi/server/dal/utiltype" +) + +func TestBuildArchiveRecords_UnescapesHTMLStrings(t *testing.T) { + t.Parallel() + + wikiValue := `[nickname|"First Hassan"] Tom & Jerry 'quote'` + nameValue := `Tom & Jerry ` + nameCNValue := `6' 1"` + + subject := buildSubjectArchiveRecord(&dao.Subject{ + Name: mustScanHTMLString(t, html.EscapeString(nameValue)), + NameCN: mustScanHTMLString(t, html.EscapeString(nameCNValue)), + Infobox: mustScanHTMLString(t, html.EscapeString(wikiValue)), + }, nil, nil, "", 0) + person := buildPersonArchiveRecord(&dao.Person{ + Name: mustScanHTMLString(t, html.EscapeString(nameValue)), + Infobox: mustScanHTMLString(t, html.EscapeString(wikiValue)), + }) + character := buildCharacterArchiveRecord(&dao.Character{ + Name: mustScanHTMLString(t, html.EscapeString(nameValue)), + Infobox: mustScanHTMLString(t, html.EscapeString(wikiValue)), + }) + + require.Equal(t, nameValue, subject.Name) + require.Equal(t, nameCNValue, subject.NameCN) + require.Equal(t, wikiValue, subject.Infobox) + require.Equal(t, nameValue, person.Name) + require.Equal(t, wikiValue, person.Infobox) + require.Equal(t, nameValue, character.Name) + require.Equal(t, wikiValue, character.Infobox) + + assertJSONRoundTrip(t, subject, func(decoded Subject) { + require.Equal(t, nameValue, decoded.Name) + require.Equal(t, nameCNValue, decoded.NameCN) + require.Equal(t, wikiValue, decoded.Infobox) + }) + assertJSONRoundTrip(t, person, func(decoded Person) { + require.Equal(t, nameValue, decoded.Name) + require.Equal(t, wikiValue, decoded.Infobox) + }) + assertJSONRoundTrip(t, character, func(decoded Character) { + require.Equal(t, nameValue, decoded.Name) + require.Equal(t, wikiValue, decoded.Infobox) + }) +} + +func assertJSONRoundTrip[T any](t *testing.T, value T, assertFn func(decoded T)) { + t.Helper() + + var buf bytes.Buffer + encode(&buf, value) + + var decoded T + require.NoError(t, json.Unmarshal(buf.Bytes(), &decoded)) + assertFn(decoded) +} + +func mustScanHTMLString(t *testing.T, value string) utiltype.HTMLEscapedString { + t.Helper() + + var result utiltype.HTMLEscapedString + require.NoError(t, (&result).Scan(value)) + + return result +} diff --git a/cmd/gen/gorm/main.go b/cmd/gen/gorm/main.go index cfe7d0c18..2a1d08ed6 100644 --- a/cmd/gen/gorm/main.go +++ b/cmd/gen/gorm/main.go @@ -24,10 +24,12 @@ NOTICE: package main import ( + "path/filepath" "strings" "gorm.io/gen" "gorm.io/gen/field" + "gorm.io/gorm" "github.com/bangumi/server/config" "github.com/bangumi/server/dal" @@ -53,28 +55,32 @@ func DeprecatedFiled(s string) gen.ModelOpt { } const createdTime = "CreatedTime" +const updateTime = "UpdatedTime" // generate code. func main() { - // specify the output directory (default: "./query") - // ### if you want to query without context constrain, set mode gen.WithoutContext ### - const dalBase = "./dal" g := gen.NewGenerator(gen.Config{ - OutPath: "./dal/query", - OutFile: "./dal/query/gen.go", - ModelPkgPath: "./dal/dao", + OutPath: filepath.Clean("./dal/query/"), + OutFile: "gen.go", + ModelPkgPath: "dao", + + WithUnitTest: false, // if you want the nullable field generation property to be pointer type, set FieldNullable true - FieldNullable: false, + FieldNullable: false, + FieldCoverable: false, + FieldSignable: false, + FieldWithIndexTag: false, // if you want to generate type tags from database, set FieldWithTypeTag true FieldWithTypeTag: true, - // if you need unit tests for query code, set WithUnitTest true - // WithUnitTest: true, + Mode: 0, }) g.WithImportPkgPath( "github.com/bangumi/server/internal/model", "github.com/bangumi/server/dal/utiltype", + "gorm.io/plugin/soft_delete", ) + g.WithJSONTagNameStrategy(func(_ string) string { return "" }) @@ -86,45 +92,64 @@ func main() { panic("failed to read config: " + err.Error()) } - conn, err := driver.NewMysqlConnectionPool(c) + conn, err := driver.NewMysqlDriver(c) if err != nil { panic(err) } - db, err := dal.NewDB(conn, c) + db, err := dal.NewGormDB(conn, c) if err != nil { panic(err) } g.UseDB(db) - dataMap := map[string]func(detailType string) (dataType string){ + dataMap := map[string]func(detailType gorm.ColumnType) (dataType string){ // bool mapping - "tinyint": func(detailType string) (dataType string) { - if strings.HasPrefix(detailType, "tinyint(1)") { + "tinyint": func(t gorm.ColumnType) (dataType string) { + dt, ok := t.ColumnType() + if !ok { + panic("failed to get column type") + } + if strings.HasPrefix(dt, "tinyint(1)") { return "bool" } - if strings.HasSuffix(detailType, "unsigned") { + if strings.HasSuffix(dt, "unsigned") { return "uint8" } return "int8" }, - "smallint": func(detailType string) (dataType string) { - if strings.HasSuffix(detailType, "unsigned") { + "smallint": func(t gorm.ColumnType) (dataType string) { + dt, ok := t.ColumnType() + if !ok { + panic("failed to get column type") + } + + if strings.HasSuffix(dt, "unsigned") { return "uint16" } return "int16" }, - "mediumint": func(detailType string) (dataType string) { - if strings.HasSuffix(detailType, "unsigned") { + "mediumint": func(t gorm.ColumnType) (dataType string) { + dt, ok := t.ColumnType() + if !ok { + panic("failed to get column type") + } + + if strings.HasSuffix(dt, "unsigned") { return "uint32" } return "int32" }, - "int": func(detailType string) (dataType string) { - if strings.HasSuffix(detailType, "unsigned") { + "int": func(t gorm.ColumnType) (dataType string) { + dt, ok := t.ColumnType() + if !ok { + panic("failed to get column type") + } + + if strings.HasSuffix(dt, "unsigned") { return "uint32" } return "int32" @@ -135,17 +160,40 @@ func main() { modelField := g.GenerateModelAs("chii_memberfields", "MemberField", gen.FieldType("uid", userIDTypeString), gen.FieldType("privacy", "[]byte"), + gen.FieldIgnore("index_sort"), + gen.FieldIgnore("user_agent"), + gen.FieldIgnore("ignorepm"), + gen.FieldIgnore("groupterms"), + gen.FieldIgnore("authstr"), + gen.FieldIgnoreReg("^(homepage|reg_source|invite_num|email_verified|reset_password_dateline|reset_password_token)$"), + gen.FieldIgnoreReg("^(reset_password_force|email_verify_dateline|email_verify_token|email_verify_score)$"), ) modelMember := g.GenerateModelAs("chii_members", "Member", gen.FieldRename("uid", "ID"), + // gen.FieldIgnore("password_crypt"), + gen.FieldIgnore("secques"), + gen.FieldIgnore("gender"), + gen.FieldIgnore("adminid"), + gen.FieldIgnore("regip"), + gen.FieldIgnore("lastip"), + + // gen.FieldIgnore("email"), + gen.FieldIgnore("bday"), + gen.FieldIgnore("styleid"), + gen.FieldIgnore("newsletter"), + gen.FieldIgnore("ukagaka_settings"), + gen.FieldIgnore("username_lock"), + gen.FieldIgnore("invited"), + gen.FieldIgnore("img_chart"), + gen.FieldType("uid", userIDTypeString), gen.FieldType("sign", "utiltype.HTMLEscapedString"), gen.FieldType("regdate", "int64"), gen.FieldType("password_crypt", "[]byte"), gen.FieldType("groupid", "uint8"), gen.FieldRelate(field.HasOne, "Fields", modelField, &field.RelateConfig{ - GORMTag: "foreignKey:uid;references:uid", + GORMTag: field.GormTag{"foreignKey": []string{"uid"}, "references": []string{"uid"}}, })) g.ApplyBasic(modelMember) @@ -188,6 +236,19 @@ func main() { gen.FieldTrimPrefix("interest_"), )) + g.ApplyBasic(g.GenerateModelAs("chii_person_collects", "PersonCollect", + gen.FieldTrimPrefix("prsn_clt_"), + gen.FieldType("prsn_clt_id", "uint32"), + gen.FieldType("prsn_clt_cat", "string"), + gen.FieldType("prsn_clt_uid", userIDTypeString), + gen.FieldType("prsn_clt_mid", "uint32"), + gen.FieldType("prsn_clt_dateline", "uint32"), + gen.FieldRename("prsn_clt_cat", "Category"), + gen.FieldRename("prsn_clt_uid", "UserID"), + gen.FieldRename("prsn_clt_mid", "TargetID"), + gen.FieldRename("prsn_clt_dateline", createdTime), + )) + g.ApplyBasic(g.GenerateModelAs("chii_index", "Index", gen.FieldTrimPrefix("idx_"), gen.FieldType("idx_id", "uint32"), @@ -200,6 +261,9 @@ func main() { gen.FieldRename("idx_replies", "ReplyCount"), gen.FieldRename("idx_collects", "CollectCount"), gen.FieldRename("idx_subject_total", "SubjectCount"), + + gen.FieldType("idx_ban", "uint8"), + gen.FieldRename("idx_ban", "Privacy"), )) g.ApplyBasic(g.GenerateModelAs("chii_index_collects", "IndexCollect", @@ -229,11 +293,13 @@ func main() { DeprecatedFiled("prsn_img_anidb"), DeprecatedFiled("prsn_anidb_id"), gen.FieldType("prsn_id", personIDTypeString), + gen.FieldType("prsn_name", "utiltype.HTMLEscapedString"), + gen.FieldType("prsn_infobox", "utiltype.HTMLEscapedString"), gen.FieldType("prsn_illustrator", "bool"), gen.FieldType("prsn_writer", "bool"), gen.FieldType("prsn_redirect", personIDTypeString), gen.FieldRelate(field.HasOne, "Fields", modelPersonField, &field.RelateConfig{ - GORMTag: "foreignKey:prsn_id;polymorphic:Owner;polymorphicValue:prsn", + GORMTag: field.GormTag{"foreignKey": []string{"prsn_id"}, "polymorphic": []string{"Owner"}, "polymorphicValue": []string{"prsn"}}, }), ) g.ApplyBasic(modelPerson) @@ -243,9 +309,11 @@ func main() { DeprecatedFiled("crt_img_anidb"), DeprecatedFiled("crt_anidb_id"), gen.FieldType("crt_id", characterIDTypeString), + gen.FieldType("crt_name", "utiltype.HTMLEscapedString"), + gen.FieldType("crt_infobox", "utiltype.HTMLEscapedString"), gen.FieldType("crt_redirect", characterIDTypeString), gen.FieldRelate(field.HasOne, "Fields", modelPersonField, &field.RelateConfig{ - GORMTag: "foreignKey:crt_id;polymorphic:Owner;polymorphicValue:crt", + GORMTag: field.GormTag{"foreignKey": []string{"crt_id"}, "polymorphic": []string{"Owner"}, "polymorphicValue": []string{"crt"}}, }), ) @@ -274,11 +342,14 @@ func main() { gen.FieldRename("subject_collect", "Done"), gen.FieldRename("field_infobox", "infobox"), gen.FieldType("subject_id", subjectIDTypeString), + gen.FieldType("subject_name", "utiltype.HTMLEscapedString"), + gen.FieldType("field_infobox", "utiltype.HTMLEscapedString"), + gen.FieldType("subject_name_cn", "utiltype.HTMLEscapedString"), gen.FieldType("subject_ban", "uint8"), gen.FieldType("subject_type_id", subjectTypeIDTypeString), gen.FieldType("subject_airtime", "uint8"), gen.FieldRelate(field.HasOne, "Fields", modelSubjectFields, &field.RelateConfig{ - GORMTag: "foreignKey:subject_id;references:field_sid", + GORMTag: field.GormTag{"foreignKey": []string{"subject_id"}, "references": []string{"field_sid"}}, }), ) g.ApplyBasic(modelSubject) @@ -290,7 +361,7 @@ func main() { gen.FieldType("ep_type", episodeTypeTypeString), gen.FieldType("ep_subject_id", subjectIDTypeString), gen.FieldRelate(field.BelongsTo, "Subject", modelSubject, &field.RelateConfig{ - GORMTag: "foreignKey:ep_subject_id;references:subject_id", + GORMTag: field.GormTag{"foreignKey": []string{"ep_subject_id"}, "references": []string{"subject_id"}}, }), )) @@ -312,7 +383,7 @@ func main() { gen.FieldType("rlt_subject_type_id", subjectTypeIDTypeString), gen.FieldType("rlt_related_subject_type_id", subjectTypeIDTypeString), gen.FieldRelate(field.HasOne, "Subject", modelSubject, &field.RelateConfig{ - GORMTag: "foreignKey:rlt_related_subject_id;references:subject_id", + GORMTag: field.GormTag{"foreignKey": []string{"rlt_related_subject_id"}, "references": []string{"subject_id"}}, }), )) @@ -323,7 +394,7 @@ func main() { gen.FieldType("rev_creator", userIDTypeString), gen.FieldType("rev_subject_id", subjectIDTypeString), gen.FieldRelate(field.BelongsTo, "Subject", modelSubject, &field.RelateConfig{ - GORMTag: "foreignKey:rev_subject_id;references:subject_id", + GORMTag: field.GormTag{"foreignKey": []string{"rev_subject_id"}, "references": []string{"subject_id"}}, }), )) @@ -333,28 +404,30 @@ func main() { gen.FieldType("crt_id", characterIDTypeString), gen.FieldType("prsn_id", personIDTypeString), gen.FieldType("subject_id", subjectIDTypeString), + gen.FieldType("rlt_type", "uint8"), gen.FieldRelate(field.HasOne, "Character", modelCharacter, &field.RelateConfig{ - GORMTag: "foreignKey:crt_id;references:crt_id", + GORMTag: field.GormTag{"foreignKey": []string{"crt_id"}, "references": []string{"crt_id"}}, }), gen.FieldRelate(field.HasOne, "Subject", modelSubject, &field.RelateConfig{ - GORMTag: "foreignKey:subject_id;references:subject_id", + GORMTag: field.GormTag{"foreignKey": []string{"subject_id"}, "references": []string{"subject_id"}}, }), gen.FieldRelate(field.HasOne, "Person", modelPerson, &field.RelateConfig{ - GORMTag: "foreignKey:prsn_id;references:prsn_id", + GORMTag: field.GormTag{"foreignKey": []string{"prsn_id"}, "references": []string{"prsn_id"}}, }), )) g.ApplyBasic( g.GenerateModelAs("chii_crt_subject_index", "CharacterSubjects", + gen.FieldIgnore("ctr_appear_eps"), gen.FieldRename("crt_id", "CharacterID"), gen.FieldType("crt_id", characterIDTypeString), gen.FieldType("subject_id", subjectIDTypeString), gen.FieldType("subject_type_id", subjectTypeIDTypeString), gen.FieldRelate(field.HasOne, "Character", modelCharacter, &field.RelateConfig{ - GORMTag: "foreignKey:crt_id;references:crt_id", + GORMTag: field.GormTag{"foreignKey": []string{"crt_id"}, "references": []string{"crt_id"}}, }), gen.FieldRelate(field.HasOne, "Subject", modelSubject, &field.RelateConfig{ - GORMTag: "foreignKey:subject_id;references:subject_id", + GORMTag: field.GormTag{"foreignKey": []string{"subject_id"}, "references": []string{"subject_id"}}, }), ), ) @@ -365,25 +438,44 @@ func main() { gen.FieldType("subject_id", subjectIDTypeString), gen.FieldType("subject_type_id", subjectTypeIDTypeString), gen.FieldRelate(field.HasOne, "Subject", modelSubject, &field.RelateConfig{ - GORMTag: "foreignKey:subject_id;references:subject_id", + GORMTag: field.GormTag{"foreignKey": []string{"subject_id"}, "references": []string{"subject_id"}}, }), gen.FieldRelate(field.HasOne, "Person", modelPerson, &field.RelateConfig{ - GORMTag: "foreignKey:prsn_id;references:prsn_id", + GORMTag: field.GormTag{"foreignKey": []string{"prsn_id"}, "references": []string{"prsn_id"}}, }), ), ) + g.ApplyBasic(g.GenerateModelAs("chii_person_relations", "PersonRelation", + gen.FieldTrimPrefix("rlt_"), + gen.FieldRename("prsn_id", "PersonID"), + gen.FieldRename("prsn_type", "PersonType"), + gen.FieldRename("rlt_prsn_id", "RelatedPersonID"), + gen.FieldRename("rlt_prsn_type", "RelatedPersonType"), + gen.FieldRename("rlt_type", "RelationType"), + gen.FieldType("prsn_id", personIDTypeString), + gen.FieldType("rlt_spoiler", "bool"), + gen.FieldType("rlt_ended", "bool"), + gen.FieldType("rlt_vice_versa", "bool"), + )) + g.ApplyBasic(g.GenerateModelAs("chii_index_related", "IndexSubject", gen.FieldTrimPrefix("idx_rlt_"), gen.FieldType("idx_rlt_type", "uint8"), gen.FieldRelate(field.BelongsTo, "Subject", modelSubject, &field.RelateConfig{ - GORMTag: "foreignKey:idx_rlt_sid;references:subject_id", + GORMTag: field.GormTag{"foreignKey": []string{"idx_rlt_sid"}, "references": []string{"subject_id"}}, }), // 变量重命名 gen.FieldRename("idx_rlt_rid", "IndexID"), gen.FieldRename("idx_rlt_sid", "SubjectID"), gen.FieldRename("idx_rlt_type", "SubjectType"), gen.FieldRename("idx_rlt_dateline", "CreatedTime"), + gen.FieldType("idx_rlt_ban", "soft_delete.DeletedAt"), + gen.FieldRename("idx_rlt_ban", "Deleted"), + gen.FieldGORMTag("idx_rlt_ban", func(tag field.GormTag) field.GormTag { + tag["softDelete"] = []string{"flag"} + return tag + }), )) g.ApplyBasic(g.GenerateModelAs("chii_rev_text", "RevisionText", @@ -446,6 +538,24 @@ func main() { gen.FieldRename("msg_rdeleted", "DeletedByReceiver"), )) + modelTagIndex := g.GenerateModelAs("chii_tag_neue_index", "TagIndex", + gen.FieldTrimPrefix("tag_"), + gen.FieldRename("tag_dateline", createdTime), + gen.FieldRename("tag_lasttouch", updateTime), + gen.FieldType("tag_type", "uint8"), + ) + + g.ApplyBasic(modelTagIndex) + + g.ApplyBasic(g.GenerateModelAs("chii_tag_neue_list", "TagList", + gen.FieldTrimPrefix("tlt_"), + gen.FieldRename("tlt_dateline", createdTime), + + gen.FieldRelate(field.HasOne, "Tag", modelTagIndex, &field.RelateConfig{ + GORMTag: field.GormTag{"foreignKey": []string{"tag_id"}, "references": []string{"tlt_tid"}}, + }), + )) + // execute the action of code generation g.Execute() } diff --git a/cmd/web/cmd.go b/cmd/web/cmd.go index 7127a0561..dce944add 100644 --- a/cmd/web/cmd.go +++ b/cmd/web/cmd.go @@ -18,7 +18,8 @@ import ( "encoding/json" "github.com/go-resty/resty/v2" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" + "github.com/segmentio/kafka-go" "github.com/spf13/cobra" "github.com/trim21/errgo" "go.uber.org/fx" @@ -31,16 +32,15 @@ import ( "github.com/bangumi/server/internal/collections/infra" "github.com/bangumi/server/internal/episode" "github.com/bangumi/server/internal/index" - "github.com/bangumi/server/internal/notification" "github.com/bangumi/server/internal/person" "github.com/bangumi/server/internal/pkg/cache" "github.com/bangumi/server/internal/pkg/dam" "github.com/bangumi/server/internal/pkg/driver" "github.com/bangumi/server/internal/pkg/logger" - "github.com/bangumi/server/internal/pm" "github.com/bangumi/server/internal/revision" "github.com/bangumi/server/internal/search" "github.com/bangumi/server/internal/subject" + "github.com/bangumi/server/internal/tag" "github.com/bangumi/server/internal/timeline" "github.com/bangumi/server/internal/user" "github.com/bangumi/server/web" @@ -64,8 +64,8 @@ func start() error { // driver and connector fx.Provide( config.AppConfigReader(config.AppTypeHTTP), - driver.NewRedisClientWithMetrics, // redis - driver.NewMysqlConnectionPool, // mysql + driver.NewRueidisClient, // redis + driver.NewMysqlDriver, // mysql func() *resty.Client { httpClient := resty.New().SetJSONEscapeHTML(false) httpClient.JSONUnmarshal = json.Unmarshal @@ -74,22 +74,34 @@ func start() error { }, ), + fx.Invoke(dal.SetupMetrics), + dal.Module, fx.Provide( logger.Copy, cache.NewRedisCache, - character.NewMysqlRepo, - user.NewMysqlRepo, index.NewMysqlRepo, auth.NewMysqlRepo, episode.NewMysqlRepo, revision.NewMysqlRepo, infra.NewMysqlRepo, - timeline.NewMysqlRepo, pm.NewMysqlRepo, notification.NewMysqlRepo, + timeline.NewSrv, - dam.New, subject.NewMysqlRepo, subject.NewCachedRepo, person.NewMysqlRepo, + dam.New, subject.NewMysqlRepo, subject.NewCachedRepo, + character.NewMysqlRepo, person.NewMysqlRepo, + + tag.NewCachedRepo, tag.NewMysqlRepo, auth.NewService, person.NewService, search.New, ), + fx.Provide( + func(cfg config.AppConfig) *kafka.Writer { + logger.Info("new kafka stream broker") + return kafka.NewWriter(kafka.WriterConfig{ + Brokers: []string{cfg.Kafka.Broker}, + }) + }, + ), + ctrl.Module, web.Module, diff --git a/config.example.toml b/config.example.toml new file mode 100644 index 000000000..c0c84062d --- /dev/null +++ b/config.example.toml @@ -0,0 +1,37 @@ +slow-sql-duration = "10s" +nsfw-word = "里番|无码|18x|エロ" +disable-words = "办假存单|办理假证|0月租手机" +banned-domain = "lista.cc|snapmail.cc|ashotmail.com|zoutlook.com" +s3-entry-point = "" +s3-access-key = "" +s3-secret-key = "" + +http.host = "127.0.0.1" +http.port = 3_000 + +redis-url = "redis://:redis-pass@127.0.0.1:6379/0" + +[mysql] +host = "127.0.0.1" +port = "3306" +user = "user" +password = "password" +db = "bangumi" +max-connection = 4 + +[kafka] +broker = "127.0.0.1:29092" +topics = [ + "debezium.bangumi.chii_subject_fields", + "debezium.bangumi.chii_subjects", + "debezium.bangumi.chii_characters", + "debezium.bangumi.chii_persons", + "debezium.bangumi.chii_members", +] + +[search.meilisearch] +url = "" +key = "" + +[debug] +gorm = true diff --git a/config.example.yaml b/config.example.yaml deleted file mode 100644 index e342a4aba..000000000 --- a/config.example.yaml +++ /dev/null @@ -1,48 +0,0 @@ -# 完整选项见 config/config.go - -http_host: 127.0.0.1 -http_port: 3000 -web_domain: next.bgm.tv - -redis_url: "redis://:redis-pass@127.0.0.1:6379/0" - -mysql: - host: 127.0.0.1 - port: "3306" - user: user - password: password - db: bangumi - max_connection: 4 - -hcaptcha_secret_key: 0x0000000000000000000000000000000000000000 - -canal: - broker: "kafka" # or redis (stream) - - kafka_broker: "kafka://192.168.1.3:29092" - topics: # kafka topic of redis stream keys - - debezium.bangumi.chii_subject_fields - - debezium.bangumi.chii_subjects - - debezium.bangumi.chii_members - -search: - # 如果为空字符串则不会启用搜索相关的功能。 - meilisearch: - url: "" - key: "" - -# log slow sql, in go time.Duration format string -# empty or zero value will disable slow sql logging -slow_sql_duration: "10s" - -debug: - gorm: true -nsfw_word: "里番|无码|18x|エロ" -disable_words: "办假存单|办理假证|0月租手机" -banned_domain: "lista.cc|snapmail.cc|ashotmail.com|zoutlook.com" - -# Optional, not required - -s3_entry_point: "" -s3_access_key: "" -s3_secret_key: "" diff --git a/config/config.go b/config/config.go index 00972a44f..e88a22f93 100644 --- a/config/config.go +++ b/config/config.go @@ -24,61 +24,60 @@ const AppTypeHTTP = "http" type AppConfig struct { Debug struct { - Gorm bool `yaml:"gorm"` - } `yaml:"debug"` + Gorm bool `toml:"gorm"` + } `toml:"debug"` - RedisURL string `yaml:"redis_url" env:"REDIS_URI" env-default:"redis://127.0.0.1:6379/0"` + RedisURL string `toml:"redis-url" env:"REDIS_URI" env-default:"redis://127.0.0.1:6379/0"` Mysql struct { - Host string `yaml:"host" env:"MYSQL_HOST" env-default:"127.0.0.1"` - Port string `yaml:"port" env:"MYSQL_PORT" env-default:"3306"` - UserName string `yaml:"user" env:"MYSQL_USER" env-default:"user"` - Password string `yaml:"password" env:"MYSQL_PASS" env-default:"password"` - Database string `yaml:"db" env:"MYSQL_DB" env-default:"bangumi"` - MaxConn int `yaml:"max_connection" env:"MYSQL_MAX_CONNECTION" env-default:"4"` - MaxIdleTime time.Duration `yaml:"conn_max_idle_time" env-default:"4h"` - MaxLifeTime time.Duration `yaml:"conn_max_life_time" env-default:"6h"` - - SlowSQLDuration time.Duration `yaml:"slow_sql_duration" env:"SLOW_SQL_DURATION"` - } `yaml:"mysql"` - - WebDomain string `yaml:"web_domain" env:"WEB_DOMAIN"` // new frontend web page domain - HTTPHost string `yaml:"http_host" env:"HTTP_HOST" env-default:"127.0.0.1"` - HTTPPort int `yaml:"http_port" env:"HTTP_PORT" env-default:"3000"` - - Canal struct { - Broker string `yaml:"broker"` - - KafkaBroker string `yaml:"kafka_broker" env:"KAFKA_BROKER"` - Topics []string `yaml:"topics"` - } `yaml:"canal"` + Host string `toml:"host" env:"MYSQL_HOST" env-default:"127.0.0.1"` + Port string `toml:"port" env:"MYSQL_PORT" env-default:"3306"` + UserName string `toml:"user-name" env:"MYSQL_USER" env-default:"user"` + Password string `toml:"password" env:"MYSQL_PASS" env-default:"password"` + Database string `toml:"database" env:"MYSQL_DB" env-default:"bangumi"` + MaxConn int `toml:"max-conn" env:"MYSQL_MAX_CONNECTION" env-default:"4"` + MaxIdleTime time.Duration `toml:"max-idle-time" env-default:"4h"` + MaxLifeTime time.Duration `toml:"max-life-time" env-default:"6h"` + + SlowSQLDuration time.Duration `toml:"slow-sql-duration" env:"SLOW_SQL_DURATION"` + } `toml:"mysql"` + + HTTP struct { + Host string `toml:"host" env:"HTTP_HOST" env-default:"127.0.0.1"` + Port int `toml:"port" env:"HTTP_PORT" env-default:"3000"` + } `toml:"http"` + + RateLimit struct { + LimitLongTime time.Duration `toml:"long-time" env:"RATE_LIMIT_LONG_TIME" env-default:"1h"` + LimitWindow time.Duration `toml:"window" env:"RATE_LIMIT_WINDOW" env-default:"10m"` + LimitCount uint `toml:"count" env:"RATE_LIMIT_COUNT" env-default:"3000"` + } `toml:"rate-limit"` + + Kafka struct { + Broker string `toml:"broker" env:"KAFKA_BROKER"` + Topics []string `toml:"topics"` + } `toml:"kafka"` Search struct { MeiliSearch struct { - URL string `yaml:"url" env:"MEILISEARCH_URL"` - Key string `yaml:"key" env:"MEILISEARCH_KEY"` - } `yaml:"meilisearch"` - - SearchBatchSize int `env:"SEARCH_BATCH_SIZE" yaml:"batch_size" env-default:"100"` - SearchBatchInterval time.Duration `env:"SEARCH_BATCH_INTERVAL" yaml:"batch_interval" env-default:"10m"` - } `yaml:"search"` - - NsfwWord string `yaml:"nsfw_word"` - DisableWords string `yaml:"disable_words"` - BannedDomain string `yaml:"banned_domain"` - - // "http://localhost:2379" - EtcdAddr string `yaml:"etcd_addr" env:"ETCD_ADDR"` - EtcdNamespace string `yaml:"etcd_namespace" env:"ETCD_NAMESPACE" env-default:"/chii/services"` - - S3EntryPoint string `yaml:"s3_entry_point" env:"S3_ENTRY_POINT"` - S3AccessKey string `yaml:"s3_access_key" env:"S3_ACCESS_KEY"` - S3SecretKey string `yaml:"s3_secret_key" env:"S3_SECRET_KEY"` - S3ImageResizeBucket string `yaml:"s3_image_resize_bucket" env:"S3_IMAGE_RESIZE_BUCKET" env-default:"img-resize"` - - AppType string + URL string `toml:"url" env:"MEILISEARCH_URL"` + Key string `toml:"key" env:"MEILISEARCH_KEY"` + Timeout time.Duration `toml:"timeout" env:"MEILISEARCH_REQUEST_TIMEOUT" env-default:"2s"` + } `toml:"meilisearch"` + } `toml:"search"` + + NsfwWord string `toml:"nsfw-word"` + DisableWords string `toml:"disable-words"` + BannedDomain string `toml:"banned-domain"` + + S3EntryPoint string `toml:"s3-entry-point" env:"S3_ENTRY_POINT"` + S3AccessKey string `toml:"s3-access-key" env:"S3_ACCESS_KEY"` + S3SecretKey string `toml:"s3-secret-key" env:"S3_SECRET_KEY"` + S3ImageResizeBucket string `toml:"s3-image-resize-bucket" env:"S3_IMAGE_RESIZE_BUCKET" env-default:"img-resize"` + + AppType string `toml:"app-type"` } func (c AppConfig) ListenAddr() string { - return fmt.Sprintf("%s:%d", c.HTTPHost, c.HTTPPort) + return fmt.Sprintf("%s:%d", c.HTTP.Host, c.HTTP.Port) } diff --git a/ctrl/create_private_message.go b/ctrl/create_private_message.go deleted file mode 100644 index e5862f918..000000000 --- a/ctrl/create_private_message.go +++ /dev/null @@ -1,107 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package ctrl - -import ( - "context" - "errors" - - "github.com/samber/lo" - "github.com/trim21/errgo" - - "github.com/bangumi/server/internal/model" - "github.com/bangumi/server/internal/pkg/generic/slice" - "github.com/bangumi/server/internal/pm" - "github.com/bangumi/server/internal/user" -) - -var ErrPmBlocked = errors.New("have been blocked") -var ErrPmNotAllReceiversExist = errors.New("some receivers not exist") -var ErrPmReceiverReject = errors.New("some receivers reject private message") -var ErrPmNotAFriend = errors.New("not a friend to some receivers") - -func (ctl Ctrl) checkNeedFriendshipReceivers( - ctx context.Context, - senderID model.UserID, - receiverIDs []model.UserID, - fieldsMap map[model.UserID]user.Fields) error { - checkFriendshipList := slice.MapFilter(receiverIDs, func(id model.UserID) (model.UserID, bool) { - if fields, ok := fieldsMap[id]; ok { - if fields.Privacy.ReceivePrivateMessage == user.ReceiveFilterFriends { - return id, true - } - } - return 0, false - }) - if len(checkFriendshipList) != 0 { - ok, checkErr := ctl.user.CheckIsFriendToOthers(ctx, senderID, checkFriendshipList...) - if checkErr != nil { - return errgo.Wrap(checkErr, "dal") - } - if !ok { - return ErrPmNotAFriend - } - } - return nil -} - -func (ctl Ctrl) checkReceivers(ctx context.Context, - senderID model.UserID, - receiverIDs []model.UserID) error { - receivers, err := ctl.user.GetByIDs(ctx, receiverIDs) - if err != nil { - return errgo.Wrap(err, "dal") - } - if len(receivers) != len(receiverIDs) { - return ErrPmNotAllReceiversExist - } - fieldsMap, err := ctl.user.GetFieldsByIDs(ctx, receiverIDs) - if err != nil { - return errgo.Wrap(err, "dal") - } - for _, id := range receiverIDs { - if fields, ok := fieldsMap[id]; ok { - if lo.Contains(fields.BlockList, senderID) { - return ErrPmBlocked - } - - if fields.Privacy.ReceivePrivateMessage == user.ReceiveFilterNone { - return ErrPmReceiverReject - } - } - } - - err = ctl.checkNeedFriendshipReceivers(ctx, senderID, receiverIDs, fieldsMap) - return err -} - -func (ctl Ctrl) CreatePrivateMessage( - ctx context.Context, - senderID model.UserID, - receiverIDs []model.UserID, - relatedIDFilter pm.IDFilter, - title string, - content string) ([]pm.PrivateMessage, error) { - emptyList := make([]pm.PrivateMessage, 0) - err := ctl.checkReceivers(ctx, senderID, receiverIDs) - if err != nil { - return emptyList, errgo.Wrap(err, "dal") - } - res, err := ctl.privateMessage.Create(ctx, senderID, receiverIDs, relatedIDFilter, title, content) - if err != nil { - return emptyList, errgo.Wrap(err, "dal") - } - return res, nil -} diff --git a/ctrl/ctrl.go b/ctrl/ctrl.go index 89bea9e52..0d310c89d 100644 --- a/ctrl/ctrl.go +++ b/ctrl/ctrl.go @@ -22,7 +22,6 @@ import ( "github.com/bangumi/server/internal/episode" "github.com/bangumi/server/internal/pkg/cache" "github.com/bangumi/server/internal/pkg/dam" - "github.com/bangumi/server/internal/pm" "github.com/bangumi/server/internal/subject" "github.com/bangumi/server/internal/timeline" "github.com/bangumi/server/internal/user" @@ -38,7 +37,6 @@ func New( user user.Repo, tx dal.Transaction, dam dam.Dam, - privateMessage pm.Repo, log *zap.Logger, ) Ctrl { return Ctrl{ @@ -48,13 +46,12 @@ func New( tx: tx, dam: dam, - subjectCached: subjectCached, - user: user, - episode: episode, - subject: subject, - collection: collection, - timeline: timeline, - privateMessage: privateMessage, + subjectCached: subjectCached, + user: user, + episode: episode, + subject: subject, + collection: collection, + timeline: timeline, } } @@ -65,11 +62,10 @@ type Ctrl struct { tx dal.Transaction dam dam.Dam - subjectCached subject.CachedRepo - user user.Repo - episode episode.Repo - subject subject.Repo - collection collections.Repo - timeline timeline.Service - privateMessage pm.Repo + subjectCached subject.CachedRepo + user user.Repo + episode episode.Repo + subject subject.Repo + collection collections.Repo + timeline timeline.Service } diff --git a/ctrl/update_episode_progress.go b/ctrl/update_episode_progress.go index 63f7d5290..264b9844a 100644 --- a/ctrl/update_episode_progress.go +++ b/ctrl/update_episode_progress.go @@ -48,10 +48,12 @@ func (ctl Ctrl) UpdateEpisodesCollection( return err } - ctl.log.Info("try to update collection info", zap.Uint32("subject", subjectID), - log.User(u.ID), zap.Reflect("episodes", episodeIDs)) - - episodes, err := ctl.episode.List(ctx, subjectID, episode.Filter{}, 0, 0) + /* + GORM v1.25.0 起修复了一个 bug,但是被当成 feature 使用了。在该版本之前,Limit 0 认为不是合法的 Limit,会被从 SQL 语句中忽略 + see PR: go-gorm/gorm/pull/6191 + 因此这里需要传入 -1 作为 Limit,从而返回全部数据。GORM 会对负数过过滤,不会出现在最终的 SQL 中。 + */ + episodes, err := ctl.episode.List(ctx, subjectID, episode.Filter{}, -1, 0) if err != nil { return errgo.Wrap(err, "episodeRepo.List") } @@ -88,7 +90,11 @@ func (ctl Ctrl) UpdateEpisodesCollection( return err } - err = ctl.timeline.ChangeEpisodeStatus(ctx, u, s, e) + if t == 0 { + return nil + } + + err = ctl.timeline.ChangeEpisodeStatus(ctx, u, s, e, t) return errgo.Wrap(err, "timeline.ChangeEpisodeStatus") } @@ -123,7 +129,11 @@ func (ctl Ctrl) UpdateEpisodeCollection( return err } - err = ctl.timeline.ChangeEpisodeStatus(ctx, u, s, e) + if t == 0 { + return nil + } + + err = ctl.timeline.ChangeEpisodeStatus(ctx, u, s, e, t) return errgo.Wrap(err, "timeline.ChangeEpisodeStatus") } @@ -139,7 +149,7 @@ func (ctl Ctrl) updateEpisodesCollectionTx( return func(tx *query.Query) error { collectionTx := ctl.collection.WithQuery(tx) - _, err := collectionTx.GetSubjectCollection(ctx, u.ID, subjectID) + sc, err := collectionTx.GetSubjectCollection(ctx, u.ID, subjectID) if err != nil { if errors.Is(err, gerr.ErrNotFound) { return gerr.ErrSubjectNotCollected @@ -155,7 +165,8 @@ func (ctl Ctrl) updateEpisodesCollectionTx( epStatus := len(ec) - err = collectionTx.UpdateSubjectCollection(ctx, u.ID, subjectID, time.Now(), "", + err = collectionTx.UpdateSubjectCollection(ctx, u.ID, + model.Subject{ID: subjectID, TypeID: sc.SubjectType}, at, "", func(ctx context.Context, s *collection.Subject) (*collection.Subject, error) { s.UpdateEps(uint32(epStatus)) return s, nil diff --git a/ctrl/update_subject_collection.go b/ctrl/update_subject_collection.go index 28b5e7cc9..2603d5e84 100644 --- a/ctrl/update_subject_collection.go +++ b/ctrl/update_subject_collection.go @@ -16,16 +16,17 @@ package ctrl import ( "context" + "errors" "time" "github.com/samber/lo" "github.com/trim21/errgo" "go.uber.org/zap" + "github.com/bangumi/server/domain/gerr" "github.com/bangumi/server/internal/auth" "github.com/bangumi/server/internal/collections/domain/collection" "github.com/bangumi/server/internal/model" - "github.com/bangumi/server/internal/pkg/logger/log" "github.com/bangumi/server/internal/pkg/null" "github.com/bangumi/server/internal/subject" ) @@ -46,11 +47,15 @@ type UpdateCollectionRequest struct { func (ctl Ctrl) UpdateSubjectCollection( ctx context.Context, u auth.Auth, - subjectID model.SubjectID, + subject model.Subject, req UpdateCollectionRequest, + allowCreate bool, ) error { - ctl.log.Info("try to update collection", zap.Uint32("subject_id", subjectID), log.User(u.ID)) - err := ctl.collection.UpdateSubjectCollection(ctx, u.ID, subjectID, time.Now(), req.IP, + met := ctl.collection.UpdateSubjectCollection + if allowCreate { + met = ctl.collection.UpdateOrCreateSubjectCollection + } + err := met(ctx, u.ID, subject, time.Now(), req.IP, func(ctx context.Context, s *collection.Subject) (*collection.Subject, error) { if req.Comment.Set { s.ShadowBan(ctl.dam.NeedReview(req.Comment.Value)) @@ -79,7 +84,7 @@ func (ctl Ctrl) UpdateSubjectCollection( } if req.EpStatus.Set { - s.UpdateVols(req.EpStatus.Value) + s.UpdateEps(req.EpStatus.Value) } if req.Type.Set { @@ -91,7 +96,6 @@ func (ctl Ctrl) UpdateSubjectCollection( return nil, e } } - return s, nil }, ) @@ -99,7 +103,7 @@ func (ctl Ctrl) UpdateSubjectCollection( return err } - return ctl.mayCreateTimeline(ctx, u, req, subjectID) + return ctl.mayCreateTimeline(ctx, u, req, subject.ID) } func (ctl Ctrl) mayCreateTimeline( @@ -108,12 +112,28 @@ func (ctl Ctrl) mayCreateTimeline( req UpdateCollectionRequest, subjectID model.SubjectID, ) error { + collect, err := ctl.collection.GetSubjectCollection(ctx, u.ID, subjectID) + if err != nil { + if errors.Is(err, gerr.ErrSubjectNotCollected) { + ctl.log.Error("failed to create associated timeline, can't get collection ID", + zap.Error(err), zap.Uint32("user_id", u.ID), zap.Uint32("subject_id", subjectID)) + return nil + } + return err + } + + if collect.Private { + return nil + } + if req.Type.Set { sj, err := ctl.subjectCached.Get(ctx, subjectID, subject.Filter{}) if err != nil { return err } - err = ctl.timeline.ChangeSubjectCollection(ctx, u.ID, sj, req.Type.Default(0), req.Comment.Value, req.Rate.Value) + + err = ctl.timeline.ChangeSubjectCollection(ctx, + u.ID, sj, req.Type.Value, collect.ID, req.Comment.Value, req.Rate.Value) if err != nil { ctl.log.Error("failed to create associated timeline", zap.Error(err)) return errgo.Wrap(err, "timelineRepo.Create") diff --git a/dal/dao/chii_characters.gen.go b/dal/dao/chii_characters.gen.go index a80eb2797..4fefe3eef 100644 --- a/dal/dao/chii_characters.gen.go +++ b/dal/dao/chii_characters.gen.go @@ -4,27 +4,31 @@ package dao +import ( + "github.com/bangumi/server/dal/utiltype" +) + const TableNameCharacter = "chii_characters" // Character mapped from table type Character struct { - ID uint32 `gorm:"column:crt_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true"` - Name string `gorm:"column:crt_name;type:varchar(255);not null"` - Role uint8 `gorm:"column:crt_role;type:tinyint(4) unsigned;not null"` // 角色,机体,组织。。 - Infobox string `gorm:"column:crt_infobox;type:mediumtext;not null"` - Summary string `gorm:"column:crt_summary;type:mediumtext;not null"` - Img string `gorm:"column:crt_img;type:varchar(255);not null"` - Comment uint32 `gorm:"column:crt_comment;type:mediumint(9) unsigned;not null"` - Collects uint32 `gorm:"column:crt_collects;type:mediumint(8) unsigned;not null"` - Dateline uint32 `gorm:"column:crt_dateline;type:int(10) unsigned;not null"` - Lastpost uint32 `gorm:"column:crt_lastpost;type:int(11) unsigned;not null"` - Lock int8 `gorm:"column:crt_lock;type:tinyint(4);not null"` - ImgAnidb string `gorm:"column:crt_img_anidb;type:varchar(255);not null"` // Deprecated - AnidbID uint32 `gorm:"column:crt_anidb_id;type:mediumint(8) unsigned;not null"` // Deprecated - Ban uint8 `gorm:"column:crt_ban;type:tinyint(3) unsigned;not null"` - Redirect uint32 `gorm:"column:crt_redirect;type:int(10) unsigned;not null"` - Nsfw bool `gorm:"column:crt_nsfw;type:tinyint(1) unsigned;not null"` - Fields PersonField `gorm:"foreignKey:crt_id;polymorphic:Owner;polymorphicValue:crt" json:"fields"` + ID uint32 `gorm:"column:crt_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true" json:""` + Name utiltype.HTMLEscapedString `gorm:"column:crt_name;type:varchar(255);not null" json:""` + Role uint8 `gorm:"column:crt_role;type:tinyint(4) unsigned;not null;comment:角色,机体,组织。。" json:""` // 角色,机体,组织。。 + Infobox utiltype.HTMLEscapedString `gorm:"column:crt_infobox;type:mediumtext;not null" json:""` + Summary string `gorm:"column:crt_summary;type:mediumtext;not null" json:""` + Img string `gorm:"column:crt_img;type:varchar(255);not null" json:""` + Comment uint32 `gorm:"column:crt_comment;type:mediumint(9) unsigned;not null" json:""` + Collects uint32 `gorm:"column:crt_collects;type:mediumint(8) unsigned;not null" json:""` + Dateline uint32 `gorm:"column:crt_dateline;type:int(10) unsigned;not null" json:""` + Lastpost uint32 `gorm:"column:crt_lastpost;type:int(11) unsigned;not null" json:""` + Lock int8 `gorm:"column:crt_lock;type:tinyint(4);not null" json:""` + ImgAnidb string `gorm:"column:crt_img_anidb;type:varchar(255);not null" json:""` // Deprecated + AnidbID uint32 `gorm:"column:crt_anidb_id;type:mediumint(8) unsigned;not null" json:""` // Deprecated + Ban uint8 `gorm:"column:crt_ban;type:tinyint(3) unsigned;not null" json:""` + Redirect uint32 `gorm:"column:crt_redirect;type:int(10) unsigned;not null" json:""` + Nsfw bool `gorm:"column:crt_nsfw;type:tinyint(1) unsigned;not null" json:""` + Fields PersonField `gorm:"foreignKey:crt_id;polymorphic:Owner;polymorphicValue:crt" json:"fields"` } // TableName Character's table name diff --git a/dal/dao/chii_crt_cast_index.gen.go b/dal/dao/chii_crt_cast_index.gen.go index d1fe1a38b..ced7552fd 100644 --- a/dal/dao/chii_crt_cast_index.gen.go +++ b/dal/dao/chii_crt_cast_index.gen.go @@ -8,11 +8,12 @@ const TableNameCast = "chii_crt_cast_index" // Cast mapped from table type Cast struct { - CharacterID uint32 `gorm:"column:crt_id;type:mediumint(9) unsigned;primaryKey"` - PersonID uint32 `gorm:"column:prsn_id;type:mediumint(9) unsigned;primaryKey"` - SubjectID uint32 `gorm:"column:subject_id;type:mediumint(9) unsigned;primaryKey"` - SubjectTypeID uint8 `gorm:"column:subject_type_id;type:tinyint(3) unsigned;not null"` // 根据人物归类查询角色,动画,书籍,游戏 - Summary string `gorm:"column:summary;type:varchar(255);not null"` // 幼年,男乱马,女乱马,变身形态,少女形态。。 + RltType uint8 `gorm:"column:rlt_type;type:mediumint(8) unsigned;not null" json:""` + CharacterID uint32 `gorm:"column:crt_id;type:mediumint(9) unsigned;primaryKey" json:""` + PersonID uint32 `gorm:"column:prsn_id;type:mediumint(9) unsigned;primaryKey" json:""` + SubjectID uint32 `gorm:"column:subject_id;type:mediumint(9) unsigned;primaryKey" json:""` + SubjectTypeID uint8 `gorm:"column:subject_type_id;type:tinyint(3) unsigned;not null;comment:根据人物归类查询角色,动画,书籍,游戏" json:""` // 根据人物归类查询角色,动画,书籍,游戏 + Summary string `gorm:"column:summary;type:varchar(255);not null;comment:幼年,男乱马,女乱马,变身形态,少女形态。。" json:""` // 幼年,男乱马,女乱马,变身形态,少女形态。。 Character Character `gorm:"foreignKey:crt_id;references:crt_id" json:"character"` Subject Subject `gorm:"foreignKey:subject_id;references:subject_id" json:"subject"` Person Person `gorm:"foreignKey:prsn_id;references:prsn_id" json:"person"` diff --git a/dal/dao/chii_crt_subject_index.gen.go b/dal/dao/chii_crt_subject_index.gen.go index 4e89c9190..6b0ede846 100644 --- a/dal/dao/chii_crt_subject_index.gen.go +++ b/dal/dao/chii_crt_subject_index.gen.go @@ -8,12 +8,12 @@ const TableNameCharacterSubjects = "chii_crt_subject_index" // CharacterSubjects mapped from table type CharacterSubjects struct { - CharacterID uint32 `gorm:"column:crt_id;type:mediumint(9) unsigned;primaryKey"` - SubjectID uint32 `gorm:"column:subject_id;type:mediumint(9) unsigned;primaryKey"` - SubjectTypeID uint8 `gorm:"column:subject_type_id;type:tinyint(4) unsigned;not null"` - CrtType uint8 `gorm:"column:crt_type;type:tinyint(4) unsigned;not null"` // 主角,配角 - CtrAppearEps string `gorm:"column:ctr_appear_eps;type:mediumtext;not null"` // 可选,角色出场的的章节 - CrtOrder uint8 `gorm:"column:crt_order;type:tinyint(3) unsigned;not null"` + CharacterID uint32 `gorm:"column:crt_id;type:mediumint(9) unsigned;primaryKey" json:""` + SubjectID uint32 `gorm:"column:subject_id;type:mediumint(9) unsigned;primaryKey" json:""` + SubjectTypeID uint8 `gorm:"column:subject_type_id;type:tinyint(4) unsigned;not null" json:""` + CrtType uint8 `gorm:"column:crt_type;type:tinyint(4) unsigned;not null;comment:主角,配角" json:""` // 主角,配角 + CrtAppearEps string `gorm:"column:crt_appear_eps;type:mediumtext;not null;comment:可选,角色出场的的章节" json:""` // 可选,角色出场的的章节 + CrtOrder uint16 `gorm:"column:crt_order;type:smallint(6) unsigned;not null" json:""` Character Character `gorm:"foreignKey:crt_id;references:crt_id" json:"character"` Subject Subject `gorm:"foreignKey:subject_id;references:subject_id" json:"subject"` } diff --git a/dal/dao/chii_ep_status.gen.go b/dal/dao/chii_ep_status.gen.go index 4bbf2ab77..5d04a8b8a 100644 --- a/dal/dao/chii_ep_status.gen.go +++ b/dal/dao/chii_ep_status.gen.go @@ -8,12 +8,12 @@ const TableNameEpCollection = "chii_ep_status" // EpCollection mapped from table type EpCollection struct { - ID uint32 `gorm:"column:ep_stt_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true"` - UserID uint32 `gorm:"column:ep_stt_uid;type:mediumint(8) unsigned;not null"` - SubjectID uint32 `gorm:"column:ep_stt_sid;type:mediumint(8) unsigned;not null"` - OnPrg bool `gorm:"column:ep_stt_on_prg;type:tinyint(1) unsigned;not null"` - Status []byte `gorm:"column:ep_stt_status;type:mediumtext;not null"` - UpdatedTime uint32 `gorm:"column:ep_stt_lasttouch;type:int(10) unsigned;not null"` + ID uint32 `gorm:"column:ep_stt_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true" json:""` + UserID uint32 `gorm:"column:ep_stt_uid;type:mediumint(8) unsigned;not null" json:""` + SubjectID uint32 `gorm:"column:ep_stt_sid;type:mediumint(8) unsigned;not null" json:""` + OnPrg bool `gorm:"column:ep_stt_on_prg;type:tinyint(1) unsigned;not null" json:""` + Status []byte `gorm:"column:ep_stt_status;type:mediumtext;not null" json:""` + UpdatedTime uint32 `gorm:"column:ep_stt_lasttouch;type:int(10) unsigned;not null" json:""` } // TableName EpCollection's table name diff --git a/dal/dao/chii_episodes.gen.go b/dal/dao/chii_episodes.gen.go index e525fbd78..a442dbd30 100644 --- a/dal/dao/chii_episodes.gen.go +++ b/dal/dao/chii_episodes.gen.go @@ -8,24 +8,24 @@ const TableNameEpisode = "chii_episodes" // Episode mapped from table type Episode struct { - ID uint32 `gorm:"column:ep_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true"` - SubjectID uint32 `gorm:"column:ep_subject_id;type:mediumint(8) unsigned;not null"` - Sort float32 `gorm:"column:ep_sort;type:float unsigned;not null"` - Type uint8 `gorm:"column:ep_type;type:tinyint(1) unsigned;not null"` - Disc uint8 `gorm:"column:ep_disc;type:tinyint(3) unsigned;not null"` // 碟片数 - Name string `gorm:"column:ep_name;type:varchar(80);not null"` - NameCn string `gorm:"column:ep_name_cn;type:varchar(80);not null"` - Rate int8 `gorm:"column:ep_rate;type:tinyint(3);not null"` - Duration string `gorm:"column:ep_duration;type:varchar(80);not null"` - Airdate string `gorm:"column:ep_airdate;type:varchar(80);not null"` - Online string `gorm:"column:ep_online;type:mediumtext;not null"` - Comment uint32 `gorm:"column:ep_comment;type:mediumint(8) unsigned;not null"` - Resources uint32 `gorm:"column:ep_resources;type:mediumint(8) unsigned;not null"` - Desc string `gorm:"column:ep_desc;type:mediumtext;not null"` - Dateline uint32 `gorm:"column:ep_dateline;type:int(10) unsigned;not null"` - Lastpost uint32 `gorm:"column:ep_lastpost;type:int(10) unsigned;not null"` - Lock uint8 `gorm:"column:ep_lock;type:tinyint(3) unsigned;not null"` - Ban uint8 `gorm:"column:ep_ban;type:tinyint(3) unsigned;not null"` + ID uint32 `gorm:"column:ep_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true" json:""` + SubjectID uint32 `gorm:"column:ep_subject_id;type:mediumint(8) unsigned;not null" json:""` + Sort float32 `gorm:"column:ep_sort;type:float unsigned;not null" json:""` + Type uint8 `gorm:"column:ep_type;type:tinyint(1) unsigned;not null" json:""` + Disc uint8 `gorm:"column:ep_disc;type:tinyint(3) unsigned;not null;comment:碟片数" json:""` // 碟片数 + Name string `gorm:"column:ep_name;type:varchar(80);not null" json:""` + NameCn string `gorm:"column:ep_name_cn;type:varchar(80);not null" json:""` + Rate int8 `gorm:"column:ep_rate;type:tinyint(3);not null" json:""` + Duration string `gorm:"column:ep_duration;type:varchar(80);not null" json:""` + Airdate string `gorm:"column:ep_airdate;type:varchar(80);not null" json:""` + Online string `gorm:"column:ep_online;type:mediumtext;not null" json:""` + Comment uint32 `gorm:"column:ep_comment;type:mediumint(8) unsigned;not null" json:""` + Resources uint32 `gorm:"column:ep_resources;type:mediumint(8) unsigned;not null" json:""` + Desc string `gorm:"column:ep_desc;type:mediumtext;not null" json:""` + Dateline uint32 `gorm:"column:ep_dateline;type:int(10) unsigned;not null" json:""` + Lastpost uint32 `gorm:"column:ep_lastpost;type:int(10) unsigned;not null" json:""` + Lock uint8 `gorm:"column:ep_lock;type:tinyint(3) unsigned;not null" json:""` + Ban uint8 `gorm:"column:ep_ban;type:tinyint(3) unsigned;not null" json:""` Subject Subject `gorm:"foreignKey:ep_subject_id;references:subject_id" json:"subject"` } diff --git a/dal/dao/chii_friends.gen.go b/dal/dao/chii_friends.gen.go index 1cc232e4e..c8c72a59c 100644 --- a/dal/dao/chii_friends.gen.go +++ b/dal/dao/chii_friends.gen.go @@ -8,11 +8,11 @@ const TableNameFriend = "chii_friends" // Friend mapped from table type Friend struct { - UserID uint32 `gorm:"column:frd_uid;type:mediumint(8) unsigned;not null"` - FriendID uint32 `gorm:"column:frd_fid;type:mediumint(8) unsigned;not null"` - Grade uint8 `gorm:"column:frd_grade;type:tinyint(3) unsigned;not null;default:1"` - CreatedTime uint32 `gorm:"column:frd_dateline;type:int(10) unsigned;not null"` - Description string `gorm:"column:frd_description;type:char(255);not null"` + UserID uint32 `gorm:"column:frd_uid;type:mediumint(8) unsigned;not null" json:""` + FriendID uint32 `gorm:"column:frd_fid;type:mediumint(8) unsigned;not null" json:""` + Grade uint8 `gorm:"column:frd_grade;type:tinyint(3) unsigned;not null;default:1" json:""` + CreatedTime uint32 `gorm:"column:frd_dateline;type:int(10) unsigned;not null" json:""` + Description string `gorm:"column:frd_description;type:char(255);not null" json:""` } // TableName Friend's table name diff --git a/dal/dao/chii_index.gen.go b/dal/dao/chii_index.gen.go index 457224780..7d16c8dcf 100644 --- a/dal/dao/chii_index.gen.go +++ b/dal/dao/chii_index.gen.go @@ -8,18 +8,19 @@ const TableNameIndex = "chii_index" // Index mapped from table type Index struct { - ID uint32 `gorm:"column:idx_id;type:mediumint(8);primaryKey;autoIncrement:true"` // 自动id - Type uint8 `gorm:"column:idx_type;type:tinyint(3) unsigned;not null"` - Title string `gorm:"column:idx_title;type:varchar(80);not null"` // 标题 - Desc string `gorm:"column:idx_desc;type:mediumtext;not null"` // 简介 - ReplyCount uint32 `gorm:"column:idx_replies;type:mediumint(8) unsigned;not null"` // 回复数 - SubjectCount uint32 `gorm:"column:idx_subject_total;type:mediumint(8) unsigned;not null"` // 内含条目总数 - CollectCount uint32 `gorm:"column:idx_collects;type:mediumint(8);not null"` // 收藏数 - Stats string `gorm:"column:idx_stats;type:mediumtext;not null"` - CreatedTime int32 `gorm:"column:idx_dateline;type:int(10);not null"` // 创建时间 - UpdatedTime uint32 `gorm:"column:idx_lasttouch;type:int(10) unsigned;not null"` - CreatorID uint32 `gorm:"column:idx_uid;type:mediumint(8);not null"` // 创建人UID - Ban bool `gorm:"column:idx_ban;type:tinyint(1) unsigned;not null"` + ID uint32 `gorm:"column:idx_id;type:mediumint(8);primaryKey;autoIncrement:true;comment:自动id" json:""` // 自动id + Type uint8 `gorm:"column:idx_type;type:tinyint(3) unsigned;not null" json:""` + Title string `gorm:"column:idx_title;type:varchar(80);not null;comment:标题" json:""` // 标题 + Desc string `gorm:"column:idx_desc;type:mediumtext;not null;comment:简介" json:""` // 简介 + ReplyCount uint32 `gorm:"column:idx_replies;type:mediumint(8) unsigned;not null;comment:回复数" json:""` // 回复数 + SubjectCount uint32 `gorm:"column:idx_subject_total;type:mediumint(8) unsigned;not null;comment:内含条目总数" json:""` // 内含条目总数 + CollectCount uint32 `gorm:"column:idx_collects;type:mediumint(8);not null;comment:收藏数" json:""` // 收藏数 + Stats string `gorm:"column:idx_stats;type:mediumtext;not null" json:""` + Award uint32 `gorm:"column:idx_award;type:mediumint(8) unsigned;not null" json:""` + CreatedTime int32 `gorm:"column:idx_dateline;type:int(10);not null;comment:创建时间" json:""` // 创建时间 + UpdatedTime uint32 `gorm:"column:idx_lasttouch;type:int(10) unsigned;not null" json:""` + CreatorID uint32 `gorm:"column:idx_uid;type:mediumint(8);not null;comment:创建人UID" json:""` // 创建人UID + Privacy uint8 `gorm:"column:idx_ban;type:tinyint(1) unsigned;not null" json:""` } // TableName Index's table name diff --git a/dal/dao/chii_index_collects.gen.go b/dal/dao/chii_index_collects.gen.go index 8f9fa2213..8aefc4e17 100644 --- a/dal/dao/chii_index_collects.gen.go +++ b/dal/dao/chii_index_collects.gen.go @@ -6,12 +6,12 @@ package dao const TableNameIndexCollect = "chii_index_collects" -// IndexCollect mapped from table +// IndexCollect 目录收藏 type IndexCollect struct { - CltID uint32 `gorm:"column:idx_clt_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true"` - IndexID uint32 `gorm:"column:idx_clt_mid;type:mediumint(8) unsigned;not null"` // 目录ID - UserID uint32 `gorm:"column:idx_clt_uid;type:mediumint(8) unsigned;not null"` // 用户UID - CreatedTime uint32 `gorm:"column:idx_clt_dateline;type:int(10) unsigned;not null"` + CltID uint32 `gorm:"column:idx_clt_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true" json:""` + IndexID uint32 `gorm:"column:idx_clt_mid;type:mediumint(8) unsigned;not null;comment:目录ID" json:""` // 目录ID + UserID uint32 `gorm:"column:idx_clt_uid;type:mediumint(8) unsigned;not null;comment:用户UID" json:""` // 用户UID + CreatedTime uint32 `gorm:"column:idx_clt_dateline;type:int(10) unsigned;not null" json:""` } // TableName IndexCollect's table name diff --git a/dal/dao/chii_index_related.gen.go b/dal/dao/chii_index_related.gen.go index aa680ed02..ee5e5df2c 100644 --- a/dal/dao/chii_index_related.gen.go +++ b/dal/dao/chii_index_related.gen.go @@ -4,19 +4,25 @@ package dao +import ( + "gorm.io/plugin/soft_delete" +) + const TableNameIndexSubject = "chii_index_related" -// IndexSubject mapped from table +// IndexSubject 目录关联表 type IndexSubject struct { - ID uint32 `gorm:"column:idx_rlt_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true"` - Cat int8 `gorm:"column:idx_rlt_cat;type:tinyint(3);not null"` - IndexID uint32 `gorm:"column:idx_rlt_rid;type:mediumint(8) unsigned;not null"` // 关联目录 - SubjectType uint8 `gorm:"column:idx_rlt_type;type:smallint(6) unsigned;not null"` // 关联条目类型 - SubjectID uint32 `gorm:"column:idx_rlt_sid;type:mediumint(8) unsigned;not null"` // 关联条目ID - Order uint32 `gorm:"column:idx_rlt_order;type:mediumint(8) unsigned;not null"` - Comment string `gorm:"column:idx_rlt_comment;type:mediumtext;not null"` - CreatedTime uint32 `gorm:"column:idx_rlt_dateline;type:int(10) unsigned;not null"` - Subject Subject `gorm:"foreignKey:idx_rlt_sid;references:subject_id" json:"subject"` + ID uint32 `gorm:"column:idx_rlt_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true" json:""` + Cat int8 `gorm:"column:idx_rlt_cat;type:tinyint(3);not null" json:""` + IndexID uint32 `gorm:"column:idx_rlt_rid;type:mediumint(8) unsigned;not null;comment:关联目录" json:""` // 关联目录 + SubjectType uint8 `gorm:"column:idx_rlt_type;type:smallint(6) unsigned;not null;comment:关联条目类型" json:""` // 关联条目类型 + SubjectID uint32 `gorm:"column:idx_rlt_sid;type:mediumint(8) unsigned;not null;comment:关联条目ID" json:""` // 关联条目ID + Order uint32 `gorm:"column:idx_rlt_order;type:mediumint(8) unsigned;not null" json:""` + Award string `gorm:"column:idx_rlt_award;type:varchar(255);not null" json:""` + Comment string `gorm:"column:idx_rlt_comment;type:mediumtext;not null" json:""` + CreatedTime uint32 `gorm:"column:idx_rlt_dateline;type:int(10) unsigned;not null" json:""` + Deleted soft_delete.DeletedAt `gorm:"column:idx_rlt_ban;type:tinyint(1) unsigned;not null;softDelete:flag" json:""` + Subject Subject `gorm:"foreignKey:idx_rlt_sid;references:subject_id" json:"subject"` } // TableName IndexSubject's table name diff --git a/dal/dao/chii_memberfields.gen.go b/dal/dao/chii_memberfields.gen.go index 028fa6175..e65745250 100644 --- a/dal/dao/chii_memberfields.gen.go +++ b/dal/dao/chii_memberfields.gen.go @@ -8,12 +8,12 @@ const TableNameMemberField = "chii_memberfields" // MemberField mapped from table type MemberField struct { - UID uint32 `gorm:"column:uid;type:mediumint(8) unsigned;primaryKey"` - Site string `gorm:"column:site;type:varchar(75);not null"` - Location string `gorm:"column:location;type:varchar(30);not null"` - Bio string `gorm:"column:bio;type:text;not null"` - Privacy []byte `gorm:"column:privacy;type:mediumtext;not null"` - Blocklist string `gorm:"column:blocklist;type:mediumtext;not null"` + UID uint32 `gorm:"column:uid;type:mediumint(8) unsigned;primaryKey" json:""` + Site string `gorm:"column:site;type:varchar(75);not null" json:""` + Location string `gorm:"column:location;type:varchar(30);not null" json:""` + Bio string `gorm:"column:bio;type:text;not null" json:""` + Privacy []byte `gorm:"column:privacy;type:mediumtext;not null" json:""` + Blocklist string `gorm:"column:blocklist;type:mediumtext;not null" json:""` } // TableName MemberField's table name diff --git a/dal/dao/chii_members.gen.go b/dal/dao/chii_members.gen.go index 49123a172..b5cdebd31 100644 --- a/dal/dao/chii_members.gen.go +++ b/dal/dao/chii_members.gen.go @@ -12,23 +12,24 @@ const TableNameMember = "chii_members" // Member mapped from table type Member struct { - ID uint32 `gorm:"column:uid;type:mediumint(8) unsigned;primaryKey;autoIncrement:true"` - Username string `gorm:"column:username;type:char(15);not null"` - Nickname string `gorm:"column:nickname;type:varchar(30);not null"` - Avatar string `gorm:"column:avatar;type:varchar(255);not null"` - Groupid uint8 `gorm:"column:groupid;type:smallint(6) unsigned;not null"` - Regdate int64 `gorm:"column:regdate;type:int(10) unsigned;not null"` - Lastvisit uint32 `gorm:"column:lastvisit;type:int(10) unsigned;not null"` - Lastactivity uint32 `gorm:"column:lastactivity;type:int(10) unsigned;not null"` - Lastpost uint32 `gorm:"column:lastpost;type:int(10) unsigned;not null"` - Dateformat string `gorm:"column:dateformat;type:char(10);not null"` - Timeformat bool `gorm:"column:timeformat;type:tinyint(1);not null"` - Timeoffset string `gorm:"column:timeoffset;type:char(4);not null"` - Newpm bool `gorm:"column:newpm;type:tinyint(1);not null"` - NewNotify uint16 `gorm:"column:new_notify;type:smallint(6) unsigned;not null"` // 新提醒 - Sign utiltype.HTMLEscapedString `gorm:"column:sign;type:varchar(255);not null"` - PasswordCrypt []byte `gorm:"column:password_crypt;type:char(64);not null"` - Email string `gorm:"column:email;type:char(50);not null"` + ID uint32 `gorm:"column:uid;type:mediumint(8) unsigned;primaryKey;autoIncrement:true" json:""` + Username string `gorm:"column:username;type:char(15);not null" json:""` + Nickname string `gorm:"column:nickname;type:varchar(30);not null" json:""` + Avatar string `gorm:"column:avatar;type:varchar(255);not null" json:""` + Groupid uint8 `gorm:"column:groupid;type:smallint(6) unsigned;not null" json:""` + Regdate int64 `gorm:"column:regdate;type:int(10) unsigned;not null" json:""` + Lastvisit uint32 `gorm:"column:lastvisit;type:int(10) unsigned;not null" json:""` + Lastactivity uint32 `gorm:"column:lastactivity;type:int(10) unsigned;not null" json:""` + Lastpost uint32 `gorm:"column:lastpost;type:int(10) unsigned;not null" json:""` + Dateformat string `gorm:"column:dateformat;type:char(10);not null" json:""` + Timeformat bool `gorm:"column:timeformat;type:tinyint(1);not null" json:""` + Timeoffset string `gorm:"column:timeoffset;type:char(4);not null" json:""` + Newpm bool `gorm:"column:newpm;type:tinyint(1);not null" json:""` + NewNotify uint16 `gorm:"column:new_notify;type:smallint(6) unsigned;not null;comment:新提醒" json:""` // 新提醒 + Sign utiltype.HTMLEscapedString `gorm:"column:sign;type:varchar(255);not null" json:""` + PasswordCrypt []byte `gorm:"column:password_crypt;type:char(64);not null" json:""` + Email string `gorm:"column:email;type:char(50);not null" json:""` + Acl string `gorm:"column:acl;type:mediumtext;not null" json:""` Fields MemberField `gorm:"foreignKey:uid;references:uid" json:"fields"` } diff --git a/dal/dao/chii_notify.gen.go b/dal/dao/chii_notify.gen.go index d41065d39..cc5d7a1f1 100644 --- a/dal/dao/chii_notify.gen.go +++ b/dal/dao/chii_notify.gen.go @@ -8,14 +8,14 @@ const TableNameNotification = "chii_notify" // Notification mapped from table type Notification struct { - ID uint32 `gorm:"column:nt_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true"` - ReceiverID uint32 `gorm:"column:nt_uid;type:mediumint(8) unsigned;not null"` - SenderID uint32 `gorm:"column:nt_from_uid;type:mediumint(8) unsigned;not null"` - Status uint8 `gorm:"column:nt_status;type:tinyint(1) unsigned;not null;default:1"` - Type uint8 `gorm:"column:nt_type;type:tinyint(3) unsigned;not null"` - FieldID uint32 `gorm:"column:nt_mid;type:mediumint(8) unsigned;not null"` // ID in notify_field - RelatedID uint32 `gorm:"column:nt_related_id;type:int(10) unsigned;not null"` - CreatedTime uint32 `gorm:"column:nt_dateline;type:int(10) unsigned;not null"` + ID uint32 `gorm:"column:nt_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true" json:""` + ReceiverID uint32 `gorm:"column:nt_uid;type:mediumint(8) unsigned;not null" json:""` + SenderID uint32 `gorm:"column:nt_from_uid;type:mediumint(8) unsigned;not null" json:""` + Status uint8 `gorm:"column:nt_status;type:tinyint(1) unsigned;not null;default:1" json:""` + Type uint8 `gorm:"column:nt_type;type:tinyint(3) unsigned;not null" json:""` + FieldID uint32 `gorm:"column:nt_mid;type:mediumint(8) unsigned;not null;comment:ID in notify_field" json:""` // ID in notify_field + RelatedID uint32 `gorm:"column:nt_related_id;type:int(10) unsigned;not null" json:""` + CreatedTime uint32 `gorm:"column:nt_dateline;type:int(10) unsigned;not null" json:""` } // TableName Notification's table name diff --git a/dal/dao/chii_notify_field.gen.go b/dal/dao/chii_notify_field.gen.go index 0f502ba2b..1af835240 100644 --- a/dal/dao/chii_notify_field.gen.go +++ b/dal/dao/chii_notify_field.gen.go @@ -8,10 +8,10 @@ const TableNameNotificationField = "chii_notify_field" // NotificationField mapped from table type NotificationField struct { - ID uint32 `gorm:"column:ntf_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true"` - RelatedType uint8 `gorm:"column:ntf_hash;type:tinyint(3) unsigned;not null"` - RelatedID uint32 `gorm:"column:ntf_rid;type:int(10) unsigned;not null"` - Title string `gorm:"column:ntf_title;type:varchar(255);not null"` + ID uint32 `gorm:"column:ntf_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true" json:""` + RelatedType uint8 `gorm:"column:ntf_hash;type:tinyint(3) unsigned;not null" json:""` + RelatedID uint32 `gorm:"column:ntf_rid;type:int(10) unsigned;not null" json:""` + Title string `gorm:"column:ntf_title;type:varchar(255);not null" json:""` } // TableName NotificationField's table name diff --git a/dal/dao/chii_oauth_access_tokens.gen.go b/dal/dao/chii_oauth_access_tokens.gen.go index 5452ae918..76bc639cd 100644 --- a/dal/dao/chii_oauth_access_tokens.gen.go +++ b/dal/dao/chii_oauth_access_tokens.gen.go @@ -12,14 +12,14 @@ const TableNameAccessToken = "chii_oauth_access_tokens" // AccessToken mapped from table type AccessToken struct { - ID uint32 `gorm:"column:id;type:mediumint(8);primaryKey;autoIncrement:true"` - Type uint8 `gorm:"column:type;type:tinyint(1) unsigned;not null"` - AccessToken string `gorm:"column:access_token;type:varchar(40);not null"` - ClientID string `gorm:"column:client_id;type:varchar(80);not null"` - UserID string `gorm:"column:user_id;type:varchar(80)"` - ExpiredAt time.Time `gorm:"column:expires;type:timestamp;not null;default:CURRENT_TIMESTAMP"` - Scope *string `gorm:"column:scope;type:varchar(4000)"` - Info []byte `gorm:"column:info;type:varchar(255);not null"` + ID uint32 `gorm:"column:id;type:mediumint(8);primaryKey;autoIncrement:true" json:""` + Type uint8 `gorm:"column:type;type:tinyint(1) unsigned;not null" json:""` + AccessToken string `gorm:"column:access_token;type:varchar(40);not null" json:""` + ClientID string `gorm:"column:client_id;type:varchar(80);not null" json:""` + UserID string `gorm:"column:user_id;type:varchar(80)" json:""` + ExpiredAt time.Time `gorm:"column:expires;type:timestamp;not null;default:CURRENT_TIMESTAMP" json:""` + Scope *string `gorm:"column:scope;type:varchar(4000)" json:""` + Info []byte `gorm:"column:info;type:varchar(255);not null" json:""` } // TableName AccessToken's table name diff --git a/dal/dao/chii_os_web_sessions.gen.go b/dal/dao/chii_os_web_sessions.gen.go index 5770a5aff..c3231d3d8 100644 --- a/dal/dao/chii_os_web_sessions.gen.go +++ b/dal/dao/chii_os_web_sessions.gen.go @@ -8,11 +8,11 @@ const TableNameWebSession = "chii_os_web_sessions" // WebSession mapped from table type WebSession struct { - Key string `gorm:"column:key;type:char(64);primaryKey"` // session key - UserID uint32 `gorm:"column:user_id;type:int(10) unsigned;not null"` // uint32 user id - Value []byte `gorm:"column:value;type:mediumblob;not null"` // json encoded session data - CreatedAt int64 `gorm:"column:created_at;type:bigint(20);not null"` // int64 unix timestamp, when session is created - ExpiredAt int64 `gorm:"column:expired_at;type:bigint(20);not null"` // int64 unix timestamp, when session is expired + Key string `gorm:"column:key;type:char(64);primaryKey;comment:session key" json:""` // session key + UserID uint32 `gorm:"column:user_id;type:int(10) unsigned;not null;comment:uint32 user id" json:""` // uint32 user id + Value []byte `gorm:"column:value;type:mediumblob;not null;comment:json encoded session data" json:""` // json encoded session data + CreatedAt int64 `gorm:"column:created_at;type:bigint(20);not null;comment:int64 unix timestamp, when session is created" json:""` // int64 unix timestamp, when session is created + ExpiredAt int64 `gorm:"column:expired_at;type:bigint(20);not null;comment:int64 unix timestamp, when session is expired" json:""` // int64 unix timestamp, when session is expired } // TableName WebSession's table name diff --git a/dal/dao/chii_person_collects.gen.go b/dal/dao/chii_person_collects.gen.go new file mode 100644 index 000000000..19c9b72e8 --- /dev/null +++ b/dal/dao/chii_person_collects.gen.go @@ -0,0 +1,21 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dao + +const TableNamePersonCollect = "chii_person_collects" + +// PersonCollect 人物收藏 +type PersonCollect struct { + ID uint32 `gorm:"column:prsn_clt_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true" json:""` + Category string `gorm:"column:prsn_clt_cat;type:enum('prsn','crt');not null" json:""` + TargetID uint32 `gorm:"column:prsn_clt_mid;type:mediumint(8) unsigned;not null" json:""` + UserID uint32 `gorm:"column:prsn_clt_uid;type:mediumint(8) unsigned;not null" json:""` + CreatedTime uint32 `gorm:"column:prsn_clt_dateline;type:int(10) unsigned;not null" json:""` +} + +// TableName PersonCollect's table name +func (*PersonCollect) TableName() string { + return TableNamePersonCollect +} diff --git a/dal/dao/chii_person_cs_index.gen.go b/dal/dao/chii_person_cs_index.gen.go index 7aa50d35c..a2d3abe32 100644 --- a/dal/dao/chii_person_cs_index.gen.go +++ b/dal/dao/chii_person_cs_index.gen.go @@ -6,15 +6,15 @@ package dao const TableNamePersonSubjects = "chii_person_cs_index" -// PersonSubjects mapped from table +// PersonSubjects subjects' credits/creator & staff (c&s)index type PersonSubjects struct { - PrsnType string `gorm:"column:prsn_type;type:enum('prsn','crt');primaryKey"` - PersonID uint32 `gorm:"column:prsn_id;type:mediumint(9) unsigned;primaryKey"` - PrsnPosition uint16 `gorm:"column:prsn_position;type:smallint(5) unsigned;primaryKey"` // 监督,原案,脚本,.. - SubjectID uint32 `gorm:"column:subject_id;type:mediumint(9) unsigned;primaryKey"` - SubjectTypeID uint8 `gorm:"column:subject_type_id;type:tinyint(4) unsigned;not null"` - Summary string `gorm:"column:summary;type:mediumtext;not null"` - PrsnAppearEps string `gorm:"column:prsn_appear_eps;type:mediumtext;not null"` // 可选,人物参与的章节 + PrsnType string `gorm:"column:prsn_type;type:enum('prsn','crt');primaryKey" json:""` + PersonID uint32 `gorm:"column:prsn_id;type:mediumint(9) unsigned;primaryKey" json:""` + PrsnPosition uint16 `gorm:"column:prsn_position;type:smallint(5) unsigned;primaryKey;comment:监督,原案,脚本,.." json:""` // 监督,原案,脚本,.. + SubjectID uint32 `gorm:"column:subject_id;type:mediumint(9) unsigned;primaryKey" json:""` + SubjectTypeID uint8 `gorm:"column:subject_type_id;type:tinyint(4) unsigned;not null" json:""` + Summary string `gorm:"column:summary;type:mediumtext;not null" json:""` + PrsnAppearEps string `gorm:"column:prsn_appear_eps;type:mediumtext;not null;comment:可选,人物参与的章节" json:""` // 可选,人物参与的章节 Subject Subject `gorm:"foreignKey:subject_id;references:subject_id" json:"subject"` Person Person `gorm:"foreignKey:prsn_id;references:prsn_id" json:"person"` } diff --git a/dal/dao/chii_person_fields.gen.go b/dal/dao/chii_person_fields.gen.go index ee92f824f..1614ffff4 100644 --- a/dal/dao/chii_person_fields.gen.go +++ b/dal/dao/chii_person_fields.gen.go @@ -8,13 +8,13 @@ const TableNamePersonField = "chii_person_fields" // PersonField mapped from table type PersonField struct { - OwnerType string `gorm:"column:prsn_cat;type:enum('prsn','crt');primaryKey"` - OwnerID uint32 `gorm:"column:prsn_id;type:int(8) unsigned;primaryKey"` - Gender uint8 `gorm:"column:gender;type:tinyint(4) unsigned;not null"` - Bloodtype uint8 `gorm:"column:bloodtype;type:tinyint(4) unsigned;not null"` - BirthYear uint16 `gorm:"column:birth_year;type:year(4);not null"` - BirthMon uint8 `gorm:"column:birth_mon;type:tinyint(2) unsigned;not null"` - BirthDay uint8 `gorm:"column:birth_day;type:tinyint(2) unsigned;not null"` + OwnerType string `gorm:"column:prsn_cat;type:enum('prsn','crt');primaryKey" json:""` + OwnerID uint32 `gorm:"column:prsn_id;type:int(8) unsigned;primaryKey" json:""` + Gender uint8 `gorm:"column:gender;type:tinyint(4) unsigned;not null" json:""` + Bloodtype uint8 `gorm:"column:bloodtype;type:tinyint(4) unsigned;not null" json:""` + BirthYear uint16 `gorm:"column:birth_year;type:year(4);not null" json:""` + BirthMon uint8 `gorm:"column:birth_mon;type:tinyint(2) unsigned;not null" json:""` + BirthDay uint8 `gorm:"column:birth_day;type:tinyint(2) unsigned;not null" json:""` } // TableName PersonField's table name diff --git a/dal/dao/chii_person_relations.gen.go b/dal/dao/chii_person_relations.gen.go new file mode 100644 index 000000000..c5fd862cd --- /dev/null +++ b/dal/dao/chii_person_relations.gen.go @@ -0,0 +1,26 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dao + +const TableNamePersonRelation = "chii_person_relations" + +// PersonRelation mapped from table +type PersonRelation struct { + ID uint32 `gorm:"column:rlt_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true" json:""` + PersonType string `gorm:"column:prsn_type;type:enum('prsn','crt');not null" json:""` + PersonID uint32 `gorm:"column:prsn_id;type:mediumint(8) unsigned;not null" json:""` + RelatedPersonType string `gorm:"column:rlt_prsn_type;type:enum('prsn','crt');not null" json:""` + RelatedPersonID uint32 `gorm:"column:rlt_prsn_id;type:int(10) unsigned;not null" json:""` + RelationType uint32 `gorm:"column:rlt_type;type:int(10) unsigned;not null;comment:关联类型" json:""` // 关联类型 + Spoiler bool `gorm:"column:rlt_spoiler;type:tinyint(3) unsigned;not null" json:""` + Ended bool `gorm:"column:rlt_ended;type:tinyint(3) unsigned;not null" json:""` + ViceVersa bool `gorm:"column:rlt_vice_versa;type:tinyint(3) unsigned;not null" json:""` + Comment string `gorm:"column:rlt_comment;type:text;not null" json:""` +} + +// TableName PersonRelation's table name +func (*PersonRelation) TableName() string { + return TableNamePersonRelation +} diff --git a/dal/dao/chii_persons.gen.go b/dal/dao/chii_persons.gen.go index 34d039fe6..3244bf5f8 100644 --- a/dal/dao/chii_persons.gen.go +++ b/dal/dao/chii_persons.gen.go @@ -4,34 +4,38 @@ package dao +import ( + "github.com/bangumi/server/dal/utiltype" +) + const TableNamePerson = "chii_persons" -// Person mapped from table +// Person (现实)人物表 type Person struct { - ID uint32 `gorm:"column:prsn_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true"` - Name string `gorm:"column:prsn_name;type:varchar(255);not null"` - Type uint8 `gorm:"column:prsn_type;type:tinyint(4) unsigned;not null"` // 个人,公司,组合 - Infobox string `gorm:"column:prsn_infobox;type:mediumtext;not null"` - Producer bool `gorm:"column:prsn_producer;type:tinyint(1);not null"` - Mangaka bool `gorm:"column:prsn_mangaka;type:tinyint(1);not null"` - Artist bool `gorm:"column:prsn_artist;type:tinyint(1);not null"` - Seiyu bool `gorm:"column:prsn_seiyu;type:tinyint(1);not null"` - Writer bool `gorm:"column:prsn_writer;type:tinyint(4);not null"` // 作家 - Illustrator bool `gorm:"column:prsn_illustrator;type:tinyint(4);not null"` // 绘师 - Actor bool `gorm:"column:prsn_actor;type:tinyint(1);not null"` // 演员 - Summary string `gorm:"column:prsn_summary;type:mediumtext;not null"` - Img string `gorm:"column:prsn_img;type:varchar(255);not null"` - ImgAnidb string `gorm:"column:prsn_img_anidb;type:varchar(255);not null"` // Deprecated - Comment uint32 `gorm:"column:prsn_comment;type:mediumint(9) unsigned;not null"` - Collects uint32 `gorm:"column:prsn_collects;type:mediumint(8) unsigned;not null"` - Dateline uint32 `gorm:"column:prsn_dateline;type:int(10) unsigned;not null"` - Lastpost uint32 `gorm:"column:prsn_lastpost;type:int(11) unsigned;not null"` - Lock int8 `gorm:"column:prsn_lock;type:tinyint(4);not null"` - AnidbID uint32 `gorm:"column:prsn_anidb_id;type:mediumint(8) unsigned;not null"` // Deprecated - Ban uint8 `gorm:"column:prsn_ban;type:tinyint(3) unsigned;not null"` - Redirect uint32 `gorm:"column:prsn_redirect;type:int(10) unsigned;not null"` - Nsfw bool `gorm:"column:prsn_nsfw;type:tinyint(1) unsigned;not null"` - Fields PersonField `gorm:"foreignKey:prsn_id;polymorphic:Owner;polymorphicValue:prsn" json:"fields"` + ID uint32 `gorm:"column:prsn_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true" json:""` + Name utiltype.HTMLEscapedString `gorm:"column:prsn_name;type:varchar(255);not null" json:""` + Type uint8 `gorm:"column:prsn_type;type:tinyint(4) unsigned;not null;comment:个人,公司,组合" json:""` // 个人,公司,组合 + Infobox utiltype.HTMLEscapedString `gorm:"column:prsn_infobox;type:mediumtext;not null" json:""` + Producer bool `gorm:"column:prsn_producer;type:tinyint(1);not null" json:""` + Mangaka bool `gorm:"column:prsn_mangaka;type:tinyint(1);not null" json:""` + Artist bool `gorm:"column:prsn_artist;type:tinyint(1);not null" json:""` + Seiyu bool `gorm:"column:prsn_seiyu;type:tinyint(1);not null" json:""` + Writer bool `gorm:"column:prsn_writer;type:tinyint(4);not null;comment:作家" json:""` // 作家 + Illustrator bool `gorm:"column:prsn_illustrator;type:tinyint(4);not null;comment:绘师" json:""` // 绘师 + Actor bool `gorm:"column:prsn_actor;type:tinyint(1);not null;comment:演员" json:""` // 演员 + Summary string `gorm:"column:prsn_summary;type:mediumtext;not null" json:""` + Img string `gorm:"column:prsn_img;type:varchar(255);not null" json:""` + ImgAnidb string `gorm:"column:prsn_img_anidb;type:varchar(255);not null" json:""` // Deprecated + Comment uint32 `gorm:"column:prsn_comment;type:mediumint(9) unsigned;not null" json:""` + Collects uint32 `gorm:"column:prsn_collects;type:mediumint(8) unsigned;not null" json:""` + Dateline uint32 `gorm:"column:prsn_dateline;type:int(10) unsigned;not null" json:""` + Lastpost uint32 `gorm:"column:prsn_lastpost;type:int(11) unsigned;not null" json:""` + Lock int8 `gorm:"column:prsn_lock;type:tinyint(4);not null" json:""` + AnidbID uint32 `gorm:"column:prsn_anidb_id;type:mediumint(8) unsigned;not null" json:""` // Deprecated + Ban uint8 `gorm:"column:prsn_ban;type:tinyint(3) unsigned;not null" json:""` + Redirect uint32 `gorm:"column:prsn_redirect;type:int(10) unsigned;not null" json:""` + Nsfw bool `gorm:"column:prsn_nsfw;type:tinyint(1) unsigned;not null" json:""` + Fields PersonField `gorm:"foreignKey:prsn_id;polymorphic:Owner;polymorphicValue:prsn" json:"fields"` } // TableName Person's table name diff --git a/dal/dao/chii_pms.gen.go b/dal/dao/chii_pms.gen.go index 637eb24b3..1b648a610 100644 --- a/dal/dao/chii_pms.gen.go +++ b/dal/dao/chii_pms.gen.go @@ -8,18 +8,18 @@ const TableNamePrivateMessage = "chii_pms" // PrivateMessage mapped from table type PrivateMessage struct { - ID uint32 `gorm:"column:msg_id;type:int(10) unsigned;primaryKey;autoIncrement:true"` - SenderID uint32 `gorm:"column:msg_sid;type:mediumint(8) unsigned;not null"` - ReceiverID uint32 `gorm:"column:msg_rid;type:mediumint(8) unsigned;not null"` - Folder string `gorm:"column:msg_folder;type:enum('inbox','outbox');not null;default:inbox"` - New bool `gorm:"column:msg_new;type:tinyint(1);not null"` - Title string `gorm:"column:msg_title;type:varchar(75);not null"` - CreatedTime uint32 `gorm:"column:msg_dateline;type:int(10) unsigned;not null"` - Content string `gorm:"column:msg_message;type:text;not null"` - MainMessageID uint32 `gorm:"column:msg_related_main;type:int(10) unsigned;not null"` - RelatedMessageID uint32 `gorm:"column:msg_related;type:int(10) unsigned;not null"` - DeletedBySender bool `gorm:"column:msg_sdeleted;type:tinyint(1) unsigned;not null"` - DeletedByReceiver bool `gorm:"column:msg_rdeleted;type:tinyint(1) unsigned;not null"` + ID uint32 `gorm:"column:msg_id;type:int(10) unsigned;primaryKey;autoIncrement:true" json:""` + SenderID uint32 `gorm:"column:msg_sid;type:mediumint(8) unsigned;not null" json:""` + ReceiverID uint32 `gorm:"column:msg_rid;type:mediumint(8) unsigned;not null" json:""` + Folder string `gorm:"column:msg_folder;type:enum('inbox','outbox');not null;default:inbox" json:""` + New bool `gorm:"column:msg_new;type:tinyint(1);not null" json:""` + Title string `gorm:"column:msg_title;type:varchar(75);not null" json:""` + CreatedTime uint32 `gorm:"column:msg_dateline;type:int(10) unsigned;not null" json:""` + Content string `gorm:"column:msg_message;type:text;not null" json:""` + MainMessageID uint32 `gorm:"column:msg_related_main;type:int(10) unsigned;not null" json:""` + RelatedMessageID uint32 `gorm:"column:msg_related;type:int(10) unsigned;not null" json:""` + DeletedBySender bool `gorm:"column:msg_sdeleted;type:tinyint(1) unsigned;not null" json:""` + DeletedByReceiver bool `gorm:"column:msg_rdeleted;type:tinyint(1) unsigned;not null" json:""` } // TableName PrivateMessage's table name diff --git a/dal/dao/chii_rev_history.gen.go b/dal/dao/chii_rev_history.gen.go index 25c3789d0..a4ed6a433 100644 --- a/dal/dao/chii_rev_history.gen.go +++ b/dal/dao/chii_rev_history.gen.go @@ -8,13 +8,13 @@ const TableNameRevisionHistory = "chii_rev_history" // RevisionHistory mapped from table type RevisionHistory struct { - ID uint32 `gorm:"column:rev_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true"` - Type uint8 `gorm:"column:rev_type;type:tinyint(3) unsigned;not null"` // 条目,角色,人物 - Mid uint32 `gorm:"column:rev_mid;type:mediumint(8) unsigned;not null"` // 对应条目,人物的ID - TextID uint32 `gorm:"column:rev_text_id;type:mediumint(9) unsigned;not null"` - CreatedTime uint32 `gorm:"column:rev_dateline;type:int(10) unsigned;not null"` - CreatorID uint32 `gorm:"column:rev_creator;type:mediumint(8) unsigned;not null"` - Summary string `gorm:"column:rev_edit_summary;type:varchar(200);not null"` + ID uint32 `gorm:"column:rev_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true" json:""` + Type uint8 `gorm:"column:rev_type;type:tinyint(3) unsigned;not null;comment:条目,角色,人物" json:""` // 条目,角色,人物 + Mid uint32 `gorm:"column:rev_mid;type:mediumint(8) unsigned;not null;comment:对应条目,人物的ID" json:""` // 对应条目,人物的ID + TextID uint32 `gorm:"column:rev_text_id;type:mediumint(9) unsigned;not null" json:""` + CreatedTime uint32 `gorm:"column:rev_dateline;type:int(10) unsigned;not null" json:""` + CreatorID uint32 `gorm:"column:rev_creator;type:mediumint(8) unsigned;not null" json:""` + Summary string `gorm:"column:rev_edit_summary;type:varchar(200);not null" json:""` } // TableName RevisionHistory's table name diff --git a/dal/dao/chii_rev_text.gen.go b/dal/dao/chii_rev_text.gen.go index a2a896c06..ef850b57d 100644 --- a/dal/dao/chii_rev_text.gen.go +++ b/dal/dao/chii_rev_text.gen.go @@ -8,8 +8,8 @@ const TableNameRevisionText = "chii_rev_text" // RevisionText mapped from table type RevisionText struct { - TextID uint32 `gorm:"column:rev_text_id;type:mediumint(9) unsigned;primaryKey;autoIncrement:true"` - Text []byte `gorm:"column:rev_text;type:mediumblob;not null"` + TextID uint32 `gorm:"column:rev_text_id;type:mediumint(9) unsigned;primaryKey;autoIncrement:true" json:""` + Text []byte `gorm:"column:rev_text;type:mediumblob;not null" json:""` } // TableName RevisionText's table name diff --git a/dal/dao/chii_subject_fields.gen.go b/dal/dao/chii_subject_fields.gen.go index eb5e4aa51..b50921a6c 100644 --- a/dal/dao/chii_subject_fields.gen.go +++ b/dal/dao/chii_subject_fields.gen.go @@ -12,26 +12,26 @@ const TableNameSubjectField = "chii_subject_fields" // SubjectField mapped from table type SubjectField struct { - Sid uint32 `gorm:"column:field_sid;type:mediumint(8) unsigned;primaryKey;autoIncrement:true"` - Tid uint16 `gorm:"column:field_tid;type:smallint(6) unsigned;not null"` - Tags []byte `gorm:"column:field_tags;type:mediumtext;not null"` - Rate1 uint32 `gorm:"column:field_rate_1;type:mediumint(8) unsigned;not null"` - Rate2 uint32 `gorm:"column:field_rate_2;type:mediumint(8) unsigned;not null"` - Rate3 uint32 `gorm:"column:field_rate_3;type:mediumint(8) unsigned;not null"` - Rate4 uint32 `gorm:"column:field_rate_4;type:mediumint(8) unsigned;not null"` - Rate5 uint32 `gorm:"column:field_rate_5;type:mediumint(8) unsigned;not null"` - Rate6 uint32 `gorm:"column:field_rate_6;type:mediumint(8) unsigned;not null"` - Rate7 uint32 `gorm:"column:field_rate_7;type:mediumint(8) unsigned;not null"` - Rate8 uint32 `gorm:"column:field_rate_8;type:mediumint(8) unsigned;not null"` - Rate9 uint32 `gorm:"column:field_rate_9;type:mediumint(8) unsigned;not null"` - Rate10 uint32 `gorm:"column:field_rate_10;type:mediumint(8) unsigned;not null"` - Airtime uint8 `gorm:"column:field_airtime;type:tinyint(1) unsigned;not null"` - Rank uint32 `gorm:"column:field_rank;type:int(10) unsigned;not null"` - Year int32 `gorm:"column:field_year;type:year(4);not null"` // 放送年份 - Mon int8 `gorm:"column:field_mon;type:tinyint(2);not null"` // 放送月份 - WeekDay int8 `gorm:"column:field_week_day;type:tinyint(1);not null"` // 放送日(星期X) - Date time.Time `gorm:"column:field_date;type:date;not null"` // 放送日期 - Redirect uint32 `gorm:"column:field_redirect;type:mediumint(8) unsigned;not null"` + Sid uint32 `gorm:"column:field_sid;type:mediumint(8) unsigned;primaryKey;autoIncrement:true" json:""` + Tid uint16 `gorm:"column:field_tid;type:smallint(6) unsigned;not null" json:""` + Tags []byte `gorm:"column:field_tags;type:mediumtext;not null" json:""` + Rate1 uint32 `gorm:"column:field_rate_1;type:mediumint(8) unsigned;not null" json:""` + Rate2 uint32 `gorm:"column:field_rate_2;type:mediumint(8) unsigned;not null" json:""` + Rate3 uint32 `gorm:"column:field_rate_3;type:mediumint(8) unsigned;not null" json:""` + Rate4 uint32 `gorm:"column:field_rate_4;type:mediumint(8) unsigned;not null" json:""` + Rate5 uint32 `gorm:"column:field_rate_5;type:mediumint(8) unsigned;not null" json:""` + Rate6 uint32 `gorm:"column:field_rate_6;type:mediumint(8) unsigned;not null" json:""` + Rate7 uint32 `gorm:"column:field_rate_7;type:mediumint(8) unsigned;not null" json:""` + Rate8 uint32 `gorm:"column:field_rate_8;type:mediumint(8) unsigned;not null" json:""` + Rate9 uint32 `gorm:"column:field_rate_9;type:mediumint(8) unsigned;not null" json:""` + Rate10 uint32 `gorm:"column:field_rate_10;type:mediumint(8) unsigned;not null" json:""` + Airtime uint8 `gorm:"column:field_airtime;type:tinyint(1) unsigned;not null" json:""` + Rank uint32 `gorm:"column:field_rank;type:int(10) unsigned;not null" json:""` + Year int32 `gorm:"column:field_year;type:year(4);not null;comment:放送年份" json:""` // 放送年份 + Mon int8 `gorm:"column:field_mon;type:tinyint(2);not null;comment:放送月份" json:""` // 放送月份 + WeekDay int8 `gorm:"column:field_week_day;type:tinyint(1);not null;comment:放送日(星期X)" json:""` // 放送日(星期X) + Date time.Time `gorm:"column:field_date;type:date;not null;comment:放送日期" json:""` // 放送日期 + Redirect uint32 `gorm:"column:field_redirect;type:mediumint(8) unsigned;not null" json:""` } // TableName SubjectField's table name diff --git a/dal/dao/chii_subject_interests.gen.go b/dal/dao/chii_subject_interests.gen.go index 1cfda7b01..5dc2d9ae1 100644 --- a/dal/dao/chii_subject_interests.gen.go +++ b/dal/dao/chii_subject_interests.gen.go @@ -12,26 +12,26 @@ const TableNameSubjectCollection = "chii_subject_interests" // SubjectCollection mapped from table type SubjectCollection struct { - ID uint32 `gorm:"column:interest_id;type:int(10) unsigned;primaryKey;autoIncrement:true"` - UserID uint32 `gorm:"column:interest_uid;type:mediumint(8) unsigned;not null"` - SubjectID uint32 `gorm:"column:interest_subject_id;type:mediumint(8) unsigned;not null"` - SubjectType uint8 `gorm:"column:interest_subject_type;type:smallint(6) unsigned;not null"` - Rate uint8 `gorm:"column:interest_rate;type:tinyint(3) unsigned;not null"` - Type uint8 `gorm:"column:interest_type;type:tinyint(1) unsigned;not null"` - HasComment bool `gorm:"column:interest_has_comment;type:tinyint(1) unsigned;not null"` - Comment utiltype.HTMLEscapedString `gorm:"column:interest_comment;type:mediumtext;not null"` - Tag string `gorm:"column:interest_tag;type:mediumtext;not null"` - EpStatus uint32 `gorm:"column:interest_ep_status;type:mediumint(8) unsigned;not null"` - VolStatus uint32 `gorm:"column:interest_vol_status;type:mediumint(8) unsigned;not null"` // 卷数 - WishTime uint32 `gorm:"column:interest_wish_dateline;type:int(10) unsigned;not null"` - DoingTime uint32 `gorm:"column:interest_doing_dateline;type:int(10) unsigned;not null"` - DoneTime uint32 `gorm:"column:interest_collect_dateline;type:int(10) unsigned;not null"` - OnHoldTime uint32 `gorm:"column:interest_on_hold_dateline;type:int(10) unsigned;not null"` - DroppedTime uint32 `gorm:"column:interest_dropped_dateline;type:int(10) unsigned;not null"` - CreateIP string `gorm:"column:interest_create_ip;type:char(15);not null"` - LastUpdateIP string `gorm:"column:interest_lasttouch_ip;type:char(15);not null"` - UpdatedTime uint32 `gorm:"column:interest_lasttouch;type:int(10) unsigned;not null"` - Private uint8 `gorm:"column:interest_private;type:tinyint(1) unsigned;not null"` + ID uint32 `gorm:"column:interest_id;type:int(10) unsigned;primaryKey;autoIncrement:true" json:""` + UserID uint32 `gorm:"column:interest_uid;type:mediumint(8) unsigned;not null" json:""` + SubjectID uint32 `gorm:"column:interest_subject_id;type:mediumint(8) unsigned;not null" json:""` + SubjectType uint8 `gorm:"column:interest_subject_type;type:smallint(6) unsigned;not null" json:""` + Rate uint8 `gorm:"column:interest_rate;type:tinyint(3) unsigned;not null" json:""` + Type uint8 `gorm:"column:interest_type;type:tinyint(1) unsigned;not null" json:""` + HasComment bool `gorm:"column:interest_has_comment;type:tinyint(1) unsigned;not null" json:""` + Comment utiltype.HTMLEscapedString `gorm:"column:interest_comment;type:mediumtext;not null" json:""` + Tag string `gorm:"column:interest_tag;type:mediumtext;not null" json:""` + EpStatus uint32 `gorm:"column:interest_ep_status;type:mediumint(8) unsigned;not null" json:""` + VolStatus uint32 `gorm:"column:interest_vol_status;type:mediumint(8) unsigned;not null;comment:卷数" json:""` // 卷数 + WishTime uint32 `gorm:"column:interest_wish_dateline;type:int(10) unsigned;not null" json:""` + DoingTime uint32 `gorm:"column:interest_doing_dateline;type:int(10) unsigned;not null" json:""` + DoneTime uint32 `gorm:"column:interest_collect_dateline;type:int(10) unsigned;not null" json:""` + OnHoldTime uint32 `gorm:"column:interest_on_hold_dateline;type:int(10) unsigned;not null" json:""` + DroppedTime uint32 `gorm:"column:interest_dropped_dateline;type:int(10) unsigned;not null" json:""` + CreateIP string `gorm:"column:interest_create_ip;type:char(15);not null" json:""` + LastUpdateIP string `gorm:"column:interest_lasttouch_ip;type:char(15);not null" json:""` + UpdatedTime uint32 `gorm:"column:interest_lasttouch;type:int(10) unsigned;not null" json:""` + Private uint8 `gorm:"column:interest_private;type:tinyint(1) unsigned;not null" json:""` } // TableName SubjectCollection's table name diff --git a/dal/dao/chii_subject_relations.gen.go b/dal/dao/chii_subject_relations.gen.go index 1bcff0d54..08628db94 100644 --- a/dal/dao/chii_subject_relations.gen.go +++ b/dal/dao/chii_subject_relations.gen.go @@ -6,15 +6,15 @@ package dao const TableNameSubjectRelation = "chii_subject_relations" -// SubjectRelation mapped from table +// SubjectRelation 条目关联表 type SubjectRelation struct { - SubjectID uint32 `gorm:"column:rlt_subject_id;type:mediumint(8) unsigned;primaryKey"` // 关联主 ID - SubjectTypeID uint8 `gorm:"column:rlt_subject_type_id;type:tinyint(3) unsigned;not null"` - RelationType uint16 `gorm:"column:rlt_relation_type;type:smallint(5) unsigned;not null"` // 关联类型 - RelatedSubjectID uint32 `gorm:"column:rlt_related_subject_id;type:mediumint(8) unsigned;primaryKey"` // 关联目标 ID - RelatedSubjectTypeID uint8 `gorm:"column:rlt_related_subject_type_id;type:tinyint(3) unsigned;not null"` // 关联目标类型 - ViceVersa bool `gorm:"column:rlt_vice_versa;type:tinyint(1) unsigned;primaryKey"` - Order uint8 `gorm:"column:rlt_order;type:tinyint(3) unsigned;not null"` // 关联排序 + SubjectID uint32 `gorm:"column:rlt_subject_id;type:mediumint(8) unsigned;primaryKey;comment:关联主 ID" json:""` // 关联主 ID + SubjectTypeID uint8 `gorm:"column:rlt_subject_type_id;type:tinyint(3) unsigned;not null" json:""` + RelationType uint16 `gorm:"column:rlt_relation_type;type:smallint(5) unsigned;not null;comment:关联类型" json:""` // 关联类型 + RelatedSubjectID uint32 `gorm:"column:rlt_related_subject_id;type:mediumint(8) unsigned;primaryKey;comment:关联目标 ID" json:""` // 关联目标 ID + RelatedSubjectTypeID uint8 `gorm:"column:rlt_related_subject_type_id;type:tinyint(3) unsigned;not null;comment:关联目标类型" json:""` // 关联目标类型 + ViceVersa bool `gorm:"column:rlt_vice_versa;type:tinyint(1) unsigned;primaryKey" json:""` + Order uint16 `gorm:"column:rlt_order;type:smallint(6) unsigned;not null;comment:关联排序" json:""` // 关联排序 Subject Subject `gorm:"foreignKey:rlt_related_subject_id;references:subject_id" json:"subject"` } diff --git a/dal/dao/chii_subject_revisions.gen.go b/dal/dao/chii_subject_revisions.gen.go index afe38bd5c..57360bb8f 100644 --- a/dal/dao/chii_subject_revisions.gen.go +++ b/dal/dao/chii_subject_revisions.gen.go @@ -8,21 +8,22 @@ const TableNameSubjectRevision = "chii_subject_revisions" // SubjectRevision mapped from table type SubjectRevision struct { - ID uint32 `gorm:"column:rev_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true"` - Type uint8 `gorm:"column:rev_type;type:tinyint(3) unsigned;not null;default:1"` // 修订类型 - SubjectID uint32 `gorm:"column:rev_subject_id;type:mediumint(8) unsigned;not null"` - TypeID uint16 `gorm:"column:rev_type_id;type:smallint(6) unsigned;not null"` - CreatorID uint32 `gorm:"column:rev_creator;type:mediumint(8) unsigned;not null"` - Dateline uint32 `gorm:"column:rev_dateline;type:int(10) unsigned;not null"` - Name string `gorm:"column:rev_name;type:varchar(80);not null"` - NameCN string `gorm:"column:rev_name_cn;type:varchar(80);not null"` - FieldInfobox string `gorm:"column:rev_field_infobox;type:mediumtext;not null"` - FieldSummary string `gorm:"column:rev_field_summary;type:mediumtext;not null"` - VoteField string `gorm:"column:rev_vote_field;type:mediumtext;not null"` - FieldEps uint32 `gorm:"column:rev_field_eps;type:mediumint(8) unsigned;not null"` - EditSummary string `gorm:"column:rev_edit_summary;type:varchar(200);not null"` - Platform uint16 `gorm:"column:rev_platform;type:smallint(6) unsigned;not null"` - Subject Subject `gorm:"foreignKey:rev_subject_id;references:subject_id" json:"subject"` + ID uint32 `gorm:"column:rev_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true" json:""` + Type uint8 `gorm:"column:rev_type;type:tinyint(3) unsigned;not null;default:1;comment:修订类型" json:""` // 修订类型 + SubjectID uint32 `gorm:"column:rev_subject_id;type:mediumint(8) unsigned;not null" json:""` + TypeID uint16 `gorm:"column:rev_type_id;type:smallint(6) unsigned;not null" json:""` + CreatorID uint32 `gorm:"column:rev_creator;type:mediumint(8) unsigned;not null" json:""` + Dateline uint32 `gorm:"column:rev_dateline;type:int(10) unsigned;not null" json:""` + Name string `gorm:"column:rev_name;type:varchar(80);not null" json:""` + NameCN string `gorm:"column:rev_name_cn;type:varchar(80);not null" json:""` + FieldInfobox string `gorm:"column:rev_field_infobox;type:mediumtext;not null" json:""` + FieldMetaTags string `gorm:"column:rev_field_meta_tags;type:mediumtext;not null" json:""` + FieldSummary string `gorm:"column:rev_field_summary;type:mediumtext;not null" json:""` + VoteField string `gorm:"column:rev_vote_field;type:mediumtext;not null" json:""` + FieldEps uint32 `gorm:"column:rev_field_eps;type:mediumint(8) unsigned;not null" json:""` + EditSummary string `gorm:"column:rev_edit_summary;type:varchar(200);not null" json:""` + Platform uint16 `gorm:"column:rev_platform;type:smallint(6) unsigned;not null" json:""` + Subject Subject `gorm:"foreignKey:rev_subject_id;references:subject_id" json:"subject"` } // TableName SubjectRevision's table name diff --git a/dal/dao/chii_subjects.gen.go b/dal/dao/chii_subjects.gen.go index bf3c9baa5..f345bd642 100644 --- a/dal/dao/chii_subjects.gen.go +++ b/dal/dao/chii_subjects.gen.go @@ -4,36 +4,41 @@ package dao +import ( + "github.com/bangumi/server/dal/utiltype" +) + const TableNameSubject = "chii_subjects" // Subject mapped from table type Subject struct { - ID uint32 `gorm:"column:subject_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true"` - TypeID uint8 `gorm:"column:subject_type_id;type:smallint(6) unsigned;not null"` - Name string `gorm:"column:subject_name;type:varchar(80);not null"` - NameCN string `gorm:"column:subject_name_cn;type:varchar(80);not null"` - UID string `gorm:"column:subject_uid;type:varchar(20);not null"` // isbn / imdb - Creator uint32 `gorm:"column:subject_creator;type:mediumint(8) unsigned;not null"` - Dateline uint32 `gorm:"column:subject_dateline;type:int(10) unsigned;not null"` - Image string `gorm:"column:subject_image;type:varchar(255);not null"` - Platform uint16 `gorm:"column:subject_platform;type:smallint(6) unsigned;not null"` - Infobox string `gorm:"column:field_infobox;type:mediumtext;not null"` - Summary string `gorm:"column:field_summary;type:mediumtext;not null"` // summary - Field5 string `gorm:"column:field_5;type:mediumtext;not null"` // author summary - Volumes uint32 `gorm:"column:field_volumes;type:mediumint(8) unsigned;not null"` // 卷数 - Eps uint32 `gorm:"column:field_eps;type:mediumint(8) unsigned;not null"` - Wish uint32 `gorm:"column:subject_wish;type:mediumint(8) unsigned;not null"` - Done uint32 `gorm:"column:subject_collect;type:mediumint(8) unsigned;not null"` - Doing uint32 `gorm:"column:subject_doing;type:mediumint(8) unsigned;not null"` - OnHold uint32 `gorm:"column:subject_on_hold;type:mediumint(8) unsigned;not null"` // 搁置人数 - Dropped uint32 `gorm:"column:subject_dropped;type:mediumint(8) unsigned;not null"` // 抛弃人数 - Series bool `gorm:"column:subject_series;type:tinyint(1) unsigned;not null"` - SeriesEntry uint32 `gorm:"column:subject_series_entry;type:mediumint(8) unsigned;not null"` - IdxCn string `gorm:"column:subject_idx_cn;type:varchar(1);not null"` - Airtime uint8 `gorm:"column:subject_airtime;type:tinyint(1) unsigned;not null"` - Nsfw bool `gorm:"column:subject_nsfw;type:tinyint(1);not null"` - Ban uint8 `gorm:"column:subject_ban;type:tinyint(1) unsigned;not null"` - Fields SubjectField `gorm:"foreignKey:subject_id;references:field_sid" json:"fields"` + ID uint32 `gorm:"column:subject_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true" json:""` + TypeID uint8 `gorm:"column:subject_type_id;type:smallint(6) unsigned;not null" json:""` + Name utiltype.HTMLEscapedString `gorm:"column:subject_name;type:varchar(512);not null" json:""` + NameCN utiltype.HTMLEscapedString `gorm:"column:subject_name_cn;type:varchar(512);not null" json:""` + UID string `gorm:"column:subject_uid;type:varchar(20);not null;comment:isbn / imdb" json:""` // isbn / imdb + Creator uint32 `gorm:"column:subject_creator;type:mediumint(8) unsigned;not null" json:""` + Dateline uint32 `gorm:"column:subject_dateline;type:int(10) unsigned;not null" json:""` + Image string `gorm:"column:subject_image;type:varchar(255);not null" json:""` + Platform uint16 `gorm:"column:subject_platform;type:smallint(6) unsigned;not null" json:""` + Infobox utiltype.HTMLEscapedString `gorm:"column:field_infobox;type:mediumtext;not null" json:""` + FieldMetaTags string `gorm:"column:field_meta_tags;type:mediumtext;not null" json:""` + Summary string `gorm:"column:field_summary;type:mediumtext;not null;comment:summary" json:""` // summary + Field5 string `gorm:"column:field_5;type:mediumtext;not null;comment:author summary" json:""` // author summary + Volumes uint32 `gorm:"column:field_volumes;type:mediumint(8) unsigned;not null;comment:卷数" json:""` // 卷数 + Eps uint32 `gorm:"column:field_eps;type:mediumint(8) unsigned;not null" json:""` + Wish uint32 `gorm:"column:subject_wish;type:mediumint(8) unsigned;not null" json:""` + Done uint32 `gorm:"column:subject_collect;type:mediumint(8) unsigned;not null" json:""` + Doing uint32 `gorm:"column:subject_doing;type:mediumint(8) unsigned;not null" json:""` + OnHold uint32 `gorm:"column:subject_on_hold;type:mediumint(8) unsigned;not null;comment:搁置人数" json:""` // 搁置人数 + Dropped uint32 `gorm:"column:subject_dropped;type:mediumint(8) unsigned;not null;comment:抛弃人数" json:""` // 抛弃人数 + Series bool `gorm:"column:subject_series;type:tinyint(1) unsigned;not null" json:""` + SeriesEntry uint32 `gorm:"column:subject_series_entry;type:mediumint(8) unsigned;not null" json:""` + IdxCn string `gorm:"column:subject_idx_cn;type:varchar(1);not null" json:""` + Airtime uint8 `gorm:"column:subject_airtime;type:tinyint(1) unsigned;not null" json:""` + Nsfw bool `gorm:"column:subject_nsfw;type:tinyint(1);not null" json:""` + Ban uint8 `gorm:"column:subject_ban;type:tinyint(1) unsigned;not null" json:""` + Fields SubjectField `gorm:"foreignKey:subject_id;references:field_sid" json:"fields"` } // TableName Subject's table name diff --git a/dal/dao/chii_tag_neue_index.gen.go b/dal/dao/chii_tag_neue_index.gen.go new file mode 100644 index 000000000..142365250 --- /dev/null +++ b/dal/dao/chii_tag_neue_index.gen.go @@ -0,0 +1,23 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dao + +const TableNameTagIndex = "chii_tag_neue_index" + +// TagIndex mapped from table +type TagIndex struct { + ID uint32 `gorm:"column:tag_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true" json:""` + Name string `gorm:"column:tag_name;type:varchar(30);not null" json:""` + Cat int8 `gorm:"column:tag_cat;type:tinyint(3);not null;comment:0=条目 1=日志 2=天窗" json:""` // 0=条目 1=日志 2=天窗 + Type uint8 `gorm:"column:tag_type;type:tinyint(3);not null" json:""` + Results uint32 `gorm:"column:tag_results;type:mediumint(8) unsigned;not null" json:""` + CreatedTime uint32 `gorm:"column:tag_dateline;type:int(10) unsigned;not null" json:""` + UpdatedTime uint32 `gorm:"column:tag_lasttouch;type:int(10) unsigned;not null" json:""` +} + +// TableName TagIndex's table name +func (*TagIndex) TableName() string { + return TableNameTagIndex +} diff --git a/dal/dao/chii_tag_neue_list.gen.go b/dal/dao/chii_tag_neue_list.gen.go new file mode 100644 index 000000000..7b6499b7b --- /dev/null +++ b/dal/dao/chii_tag_neue_list.gen.go @@ -0,0 +1,23 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dao + +const TableNameTagList = "chii_tag_neue_list" + +// TagList mapped from table +type TagList struct { + Tid uint32 `gorm:"column:tlt_tid;type:mediumint(8) unsigned;not null" json:""` + UID uint32 `gorm:"column:tlt_uid;type:mediumint(8) unsigned;not null" json:""` + Cat uint8 `gorm:"column:tlt_cat;type:tinyint(3) unsigned;not null" json:""` + Type uint8 `gorm:"column:tlt_type;type:tinyint(3) unsigned;not null" json:""` + Mid uint32 `gorm:"column:tlt_mid;type:mediumint(8) unsigned;not null" json:""` + CreatedTime uint32 `gorm:"column:tlt_dateline;type:int(10) unsigned;not null" json:""` + Tag TagIndex `gorm:"foreignKey:tag_id;references:tlt_tid" json:"tag"` +} + +// TableName TagList's table name +func (*TagList) TableName() string { + return TableNameTagList +} diff --git a/dal/dao/chii_usergroup.gen.go b/dal/dao/chii_usergroup.gen.go index f56211013..8ec1230b3 100644 --- a/dal/dao/chii_usergroup.gen.go +++ b/dal/dao/chii_usergroup.gen.go @@ -8,10 +8,10 @@ const TableNameUserGroup = "chii_usergroup" // UserGroup mapped from table type UserGroup struct { - ID uint8 `gorm:"column:usr_grp_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true"` - Name string `gorm:"column:usr_grp_name;type:varchar(255);not null"` - Perm []byte `gorm:"column:usr_grp_perm;type:mediumtext;not null"` - Dateline uint32 `gorm:"column:usr_grp_dateline;type:int(10) unsigned;not null"` + ID uint8 `gorm:"column:usr_grp_id;type:mediumint(8) unsigned;primaryKey;autoIncrement:true" json:""` + Name string `gorm:"column:usr_grp_name;type:varchar(255);not null" json:""` + Perm []byte `gorm:"column:usr_grp_perm;type:mediumtext;not null" json:""` + Dateline uint32 `gorm:"column:usr_grp_dateline;type:int(10) unsigned;not null" json:""` } // TableName UserGroup's table name diff --git a/dal/fx.go b/dal/fx.go index 3810e1785..1f06120bb 100644 --- a/dal/fx.go +++ b/dal/fx.go @@ -17,6 +17,9 @@ package dal import ( + "database/sql" + + "github.com/jmoiron/sqlx" "go.uber.org/fx" "github.com/bangumi/server/dal/query" @@ -24,8 +27,11 @@ import ( var Module = fx.Module("dal", fx.Provide( - NewDB, + NewGormDB, query.Use, NewMysqlTransaction, + func(db *sql.DB) *sqlx.DB { + return sqlx.NewDb(db, "mysql") + }, ), ) diff --git a/dal/log.go b/dal/log.go index ed29dabb9..5f56bb7b1 100644 --- a/dal/log.go +++ b/dal/log.go @@ -82,7 +82,7 @@ func (l *metricsLog) Trace(_ context.Context, begin time.Time, fc func() (sql st l.h.Observe(elapsed.Seconds()) switch { - case err != nil && !errors.Is(err, gorm.ErrRecordNotFound): + case err != nil && !errors.Is(err, gorm.ErrRecordNotFound) && !errors.Is(err, context.Canceled): sql, rows := fc() l.log.Error("gorm error", zap.String("sql", sql), zap.Error(err), zap.Duration("duration", elapsed), zap.Int64("rows", rows)) diff --git a/dal/metrics.go b/dal/metrics.go index 041d6120f..4702c7a0b 100644 --- a/dal/metrics.go +++ b/dal/metrics.go @@ -22,7 +22,7 @@ import ( "gorm.io/gorm" ) -func setupMetrics(db *gorm.DB, conn *sql.DB) error { +func SetupMetrics(db *gorm.DB, conn *sql.DB) error { var DatabaseQuery = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "chii_db_execute_total", diff --git a/dal/new.go b/dal/new.go index 35de32e5a..6166a1447 100644 --- a/dal/new.go +++ b/dal/new.go @@ -16,10 +16,9 @@ package dal import ( "database/sql" - "log" - "os" "github.com/trim21/errgo" + "go.uber.org/zap" "gorm.io/driver/mysql" "gorm.io/gorm" gormLogger "gorm.io/gorm/logger" @@ -28,12 +27,12 @@ import ( "github.com/bangumi/server/internal/pkg/logger" ) -func NewDB(conn *sql.DB, c config.AppConfig) (*gorm.DB, error) { +func NewGormDB(conn *sql.DB, c config.AppConfig) (*gorm.DB, error) { var gLog gormLogger.Interface if c.Debug.Gorm { logger.Info("enable gorm debug mode, will log all sql") gLog = gormLogger.New( - log.New(os.Stdout, "\r\n", log.LstdFlags), + logger.StdAt(zap.DebugLevel), gormLogger.Config{ LogLevel: gormLogger.Info, IgnoreRecordNotFoundError: true, @@ -52,9 +51,5 @@ func NewDB(conn *sql.DB, c config.AppConfig) (*gorm.DB, error) { return nil, errgo.Wrap(err, "create dal") } - if err = setupMetrics(db, conn); err != nil { - return nil, errgo.Wrap(err, "setup metrics") - } - return db, nil } diff --git a/dal/new_test.go b/dal/new_test.go index 54f0645cc..ef29f65a8 100644 --- a/dal/new_test.go +++ b/dal/new_test.go @@ -31,9 +31,9 @@ func TestNewDB(t *testing.T) { cfg, err := config.NewAppConfig() require.NoError(t, err) - conn, err := driver.NewMysqlConnectionPool(cfg) + conn, err := driver.NewMysqlDriver(cfg) require.NoError(t, err) - db, err := dal.NewDB(conn, cfg) + db, err := dal.NewGormDB(conn, cfg) require.NoError(t, err) err = db.Exec("select 0;").Error diff --git a/dal/query/chii_characters.gen.go b/dal/query/chii_characters.gen.go index ef1338677..5d2039ee9 100644 --- a/dal/query/chii_characters.gen.go +++ b/dal/query/chii_characters.gen.go @@ -28,9 +28,9 @@ func newCharacter(db *gorm.DB, opts ...gen.DOOption) character { tableName := _character.characterDo.TableName() _character.ALL = field.NewAsterisk(tableName) _character.ID = field.NewUint32(tableName, "crt_id") - _character.Name = field.NewString(tableName, "crt_name") + _character.Name = field.NewField(tableName, "crt_name") _character.Role = field.NewUint8(tableName, "crt_role") - _character.Infobox = field.NewString(tableName, "crt_infobox") + _character.Infobox = field.NewField(tableName, "crt_infobox") _character.Summary = field.NewString(tableName, "crt_summary") _character.Img = field.NewString(tableName, "crt_img") _character.Comment = field.NewUint32(tableName, "crt_comment") @@ -59,9 +59,9 @@ type character struct { ALL field.Asterisk ID field.Uint32 - Name field.String + Name field.Field Role field.Uint8 // 角色,机体,组织。。 - Infobox field.String + Infobox field.Field Summary field.String Img field.String Comment field.Uint32 @@ -92,9 +92,9 @@ func (c character) As(alias string) *character { func (c *character) updateTableName(table string) *character { c.ALL = field.NewAsterisk(table) c.ID = field.NewUint32(table, "crt_id") - c.Name = field.NewString(table, "crt_name") + c.Name = field.NewField(table, "crt_name") c.Role = field.NewUint8(table, "crt_role") - c.Infobox = field.NewString(table, "crt_infobox") + c.Infobox = field.NewField(table, "crt_infobox") c.Summary = field.NewString(table, "crt_summary") c.Img = field.NewString(table, "crt_img") c.Comment = field.NewUint32(table, "crt_comment") @@ -121,6 +121,8 @@ func (c character) TableName() string { return c.characterDo.TableName() } func (c character) Alias() string { return c.characterDo.Alias() } +func (c character) Columns(cols ...field.Expr) gen.Columns { return c.characterDo.Columns(cols...) } + func (c *character) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := c.fieldMap[fieldName] if !ok || _f == nil { @@ -153,11 +155,14 @@ func (c *character) fillFieldMap() { func (c character) clone(db *gorm.DB) character { c.characterDo.ReplaceConnPool(db.Statement.ConnPool) + c.Fields.db = db.Session(&gorm.Session{Initialized: true}) + c.Fields.db.Statement.ConnPool = db.Statement.ConnPool return c } func (c character) replaceDB(db *gorm.DB) character { c.characterDo.ReplaceDB(db) + c.Fields.db = db.Session(&gorm.Session{}) return c } @@ -185,10 +190,20 @@ func (a characterHasOneFields) WithContext(ctx context.Context) *characterHasOne return &a } +func (a characterHasOneFields) Session(session *gorm.Session) *characterHasOneFields { + a.db = a.db.Session(session) + return &a +} + func (a characterHasOneFields) Model(m *dao.Character) *characterHasOneFieldsTx { return &characterHasOneFieldsTx{a.db.Model(m).Association(a.Name())} } +func (a characterHasOneFields) Unscoped() *characterHasOneFields { + a.db = a.db.Unscoped() + return &a +} + type characterHasOneFieldsTx struct{ tx *gorm.Association } func (a characterHasOneFieldsTx) Find() (result *dao.PersonField, err error) { @@ -227,6 +242,11 @@ func (a characterHasOneFieldsTx) Count() int64 { return a.tx.Count() } +func (a characterHasOneFieldsTx) Unscoped() *characterHasOneFieldsTx { + a.tx = a.tx.Unscoped() + return &a +} + type characterDo struct{ gen.DO } func (c characterDo) Debug() *characterDo { @@ -273,10 +293,6 @@ func (c characterDo) Where(conds ...gen.Condition) *characterDo { return c.withDO(c.DO.Where(conds...)) } -func (c characterDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *characterDo { - return c.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (c characterDo) Order(conds ...field.Expr) *characterDo { return c.withDO(c.DO.Order(conds...)) } diff --git a/dal/query/chii_crt_cast_index.gen.go b/dal/query/chii_crt_cast_index.gen.go index dc4ca58a2..94d1b4c63 100644 --- a/dal/query/chii_crt_cast_index.gen.go +++ b/dal/query/chii_crt_cast_index.gen.go @@ -27,6 +27,7 @@ func newCast(db *gorm.DB, opts ...gen.DOOption) cast { tableName := _cast.castDo.TableName() _cast.ALL = field.NewAsterisk(tableName) + _cast.RltType = field.NewUint8(tableName, "rlt_type") _cast.CharacterID = field.NewUint32(tableName, "crt_id") _cast.PersonID = field.NewUint32(tableName, "prsn_id") _cast.SubjectID = field.NewUint32(tableName, "subject_id") @@ -74,6 +75,7 @@ type cast struct { castDo castDo ALL field.Asterisk + RltType field.Uint8 CharacterID field.Uint32 PersonID field.Uint32 SubjectID field.Uint32 @@ -100,6 +102,7 @@ func (c cast) As(alias string) *cast { func (c *cast) updateTableName(table string) *cast { c.ALL = field.NewAsterisk(table) + c.RltType = field.NewUint8(table, "rlt_type") c.CharacterID = field.NewUint32(table, "crt_id") c.PersonID = field.NewUint32(table, "prsn_id") c.SubjectID = field.NewUint32(table, "subject_id") @@ -117,6 +120,8 @@ func (c cast) TableName() string { return c.castDo.TableName() } func (c cast) Alias() string { return c.castDo.Alias() } +func (c cast) Columns(cols ...field.Expr) gen.Columns { return c.castDo.Columns(cols...) } + func (c *cast) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := c.fieldMap[fieldName] if !ok || _f == nil { @@ -127,7 +132,8 @@ func (c *cast) GetFieldByName(fieldName string) (field.OrderExpr, bool) { } func (c *cast) fillFieldMap() { - c.fieldMap = make(map[string]field.Expr, 8) + c.fieldMap = make(map[string]field.Expr, 9) + c.fieldMap["rlt_type"] = c.RltType c.fieldMap["crt_id"] = c.CharacterID c.fieldMap["prsn_id"] = c.PersonID c.fieldMap["subject_id"] = c.SubjectID @@ -138,11 +144,20 @@ func (c *cast) fillFieldMap() { func (c cast) clone(db *gorm.DB) cast { c.castDo.ReplaceConnPool(db.Statement.ConnPool) + c.Character.db = db.Session(&gorm.Session{Initialized: true}) + c.Character.db.Statement.ConnPool = db.Statement.ConnPool + c.Subject.db = db.Session(&gorm.Session{Initialized: true}) + c.Subject.db.Statement.ConnPool = db.Statement.ConnPool + c.Person.db = db.Session(&gorm.Session{Initialized: true}) + c.Person.db.Statement.ConnPool = db.Statement.ConnPool return c } func (c cast) replaceDB(db *gorm.DB) cast { c.castDo.ReplaceDB(db) + c.Character.db = db.Session(&gorm.Session{}) + c.Subject.db = db.Session(&gorm.Session{}) + c.Person.db = db.Session(&gorm.Session{}) return c } @@ -174,10 +189,20 @@ func (a castHasOneCharacter) WithContext(ctx context.Context) *castHasOneCharact return &a } +func (a castHasOneCharacter) Session(session *gorm.Session) *castHasOneCharacter { + a.db = a.db.Session(session) + return &a +} + func (a castHasOneCharacter) Model(m *dao.Cast) *castHasOneCharacterTx { return &castHasOneCharacterTx{a.db.Model(m).Association(a.Name())} } +func (a castHasOneCharacter) Unscoped() *castHasOneCharacter { + a.db = a.db.Unscoped() + return &a +} + type castHasOneCharacterTx struct{ tx *gorm.Association } func (a castHasOneCharacterTx) Find() (result *dao.Character, err error) { @@ -216,6 +241,11 @@ func (a castHasOneCharacterTx) Count() int64 { return a.tx.Count() } +func (a castHasOneCharacterTx) Unscoped() *castHasOneCharacterTx { + a.tx = a.tx.Unscoped() + return &a +} + type castHasOneSubject struct { db *gorm.DB @@ -244,10 +274,20 @@ func (a castHasOneSubject) WithContext(ctx context.Context) *castHasOneSubject { return &a } +func (a castHasOneSubject) Session(session *gorm.Session) *castHasOneSubject { + a.db = a.db.Session(session) + return &a +} + func (a castHasOneSubject) Model(m *dao.Cast) *castHasOneSubjectTx { return &castHasOneSubjectTx{a.db.Model(m).Association(a.Name())} } +func (a castHasOneSubject) Unscoped() *castHasOneSubject { + a.db = a.db.Unscoped() + return &a +} + type castHasOneSubjectTx struct{ tx *gorm.Association } func (a castHasOneSubjectTx) Find() (result *dao.Subject, err error) { @@ -286,6 +326,11 @@ func (a castHasOneSubjectTx) Count() int64 { return a.tx.Count() } +func (a castHasOneSubjectTx) Unscoped() *castHasOneSubjectTx { + a.tx = a.tx.Unscoped() + return &a +} + type castHasOnePerson struct { db *gorm.DB @@ -314,10 +359,20 @@ func (a castHasOnePerson) WithContext(ctx context.Context) *castHasOnePerson { return &a } +func (a castHasOnePerson) Session(session *gorm.Session) *castHasOnePerson { + a.db = a.db.Session(session) + return &a +} + func (a castHasOnePerson) Model(m *dao.Cast) *castHasOnePersonTx { return &castHasOnePersonTx{a.db.Model(m).Association(a.Name())} } +func (a castHasOnePerson) Unscoped() *castHasOnePerson { + a.db = a.db.Unscoped() + return &a +} + type castHasOnePersonTx struct{ tx *gorm.Association } func (a castHasOnePersonTx) Find() (result *dao.Person, err error) { @@ -356,6 +411,11 @@ func (a castHasOnePersonTx) Count() int64 { return a.tx.Count() } +func (a castHasOnePersonTx) Unscoped() *castHasOnePersonTx { + a.tx = a.tx.Unscoped() + return &a +} + type castDo struct{ gen.DO } func (c castDo) Debug() *castDo { @@ -402,10 +462,6 @@ func (c castDo) Where(conds ...gen.Condition) *castDo { return c.withDO(c.DO.Where(conds...)) } -func (c castDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *castDo { - return c.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (c castDo) Order(conds ...field.Expr) *castDo { return c.withDO(c.DO.Order(conds...)) } diff --git a/dal/query/chii_crt_subject_index.gen.go b/dal/query/chii_crt_subject_index.gen.go index 5a63a9dc7..5575821fc 100644 --- a/dal/query/chii_crt_subject_index.gen.go +++ b/dal/query/chii_crt_subject_index.gen.go @@ -31,8 +31,8 @@ func newCharacterSubjects(db *gorm.DB, opts ...gen.DOOption) characterSubjects { _characterSubjects.SubjectID = field.NewUint32(tableName, "subject_id") _characterSubjects.SubjectTypeID = field.NewUint8(tableName, "subject_type_id") _characterSubjects.CrtType = field.NewUint8(tableName, "crt_type") - _characterSubjects.CtrAppearEps = field.NewString(tableName, "ctr_appear_eps") - _characterSubjects.CrtOrder = field.NewUint8(tableName, "crt_order") + _characterSubjects.CrtAppearEps = field.NewString(tableName, "crt_appear_eps") + _characterSubjects.CrtOrder = field.NewUint16(tableName, "crt_order") _characterSubjects.Character = characterSubjectsHasOneCharacter{ db: db.Session(&gorm.Session{}), @@ -68,8 +68,8 @@ type characterSubjects struct { SubjectID field.Uint32 SubjectTypeID field.Uint8 CrtType field.Uint8 // 主角,配角 - CtrAppearEps field.String // 可选,角色出场的的章节 - CrtOrder field.Uint8 + CrtAppearEps field.String // 可选,角色出场的的章节 + CrtOrder field.Uint16 Character characterSubjectsHasOneCharacter Subject characterSubjectsHasOneSubject @@ -93,8 +93,8 @@ func (c *characterSubjects) updateTableName(table string) *characterSubjects { c.SubjectID = field.NewUint32(table, "subject_id") c.SubjectTypeID = field.NewUint8(table, "subject_type_id") c.CrtType = field.NewUint8(table, "crt_type") - c.CtrAppearEps = field.NewString(table, "ctr_appear_eps") - c.CrtOrder = field.NewUint8(table, "crt_order") + c.CrtAppearEps = field.NewString(table, "crt_appear_eps") + c.CrtOrder = field.NewUint16(table, "crt_order") c.fillFieldMap() @@ -109,6 +109,10 @@ func (c characterSubjects) TableName() string { return c.characterSubjectsDo.Tab func (c characterSubjects) Alias() string { return c.characterSubjectsDo.Alias() } +func (c characterSubjects) Columns(cols ...field.Expr) gen.Columns { + return c.characterSubjectsDo.Columns(cols...) +} + func (c *characterSubjects) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := c.fieldMap[fieldName] if !ok || _f == nil { @@ -124,18 +128,24 @@ func (c *characterSubjects) fillFieldMap() { c.fieldMap["subject_id"] = c.SubjectID c.fieldMap["subject_type_id"] = c.SubjectTypeID c.fieldMap["crt_type"] = c.CrtType - c.fieldMap["ctr_appear_eps"] = c.CtrAppearEps + c.fieldMap["crt_appear_eps"] = c.CrtAppearEps c.fieldMap["crt_order"] = c.CrtOrder } func (c characterSubjects) clone(db *gorm.DB) characterSubjects { c.characterSubjectsDo.ReplaceConnPool(db.Statement.ConnPool) + c.Character.db = db.Session(&gorm.Session{Initialized: true}) + c.Character.db.Statement.ConnPool = db.Statement.ConnPool + c.Subject.db = db.Session(&gorm.Session{Initialized: true}) + c.Subject.db.Statement.ConnPool = db.Statement.ConnPool return c } func (c characterSubjects) replaceDB(db *gorm.DB) characterSubjects { c.characterSubjectsDo.ReplaceDB(db) + c.Character.db = db.Session(&gorm.Session{}) + c.Subject.db = db.Session(&gorm.Session{}) return c } @@ -167,10 +177,20 @@ func (a characterSubjectsHasOneCharacter) WithContext(ctx context.Context) *char return &a } +func (a characterSubjectsHasOneCharacter) Session(session *gorm.Session) *characterSubjectsHasOneCharacter { + a.db = a.db.Session(session) + return &a +} + func (a characterSubjectsHasOneCharacter) Model(m *dao.CharacterSubjects) *characterSubjectsHasOneCharacterTx { return &characterSubjectsHasOneCharacterTx{a.db.Model(m).Association(a.Name())} } +func (a characterSubjectsHasOneCharacter) Unscoped() *characterSubjectsHasOneCharacter { + a.db = a.db.Unscoped() + return &a +} + type characterSubjectsHasOneCharacterTx struct{ tx *gorm.Association } func (a characterSubjectsHasOneCharacterTx) Find() (result *dao.Character, err error) { @@ -209,6 +229,11 @@ func (a characterSubjectsHasOneCharacterTx) Count() int64 { return a.tx.Count() } +func (a characterSubjectsHasOneCharacterTx) Unscoped() *characterSubjectsHasOneCharacterTx { + a.tx = a.tx.Unscoped() + return &a +} + type characterSubjectsHasOneSubject struct { db *gorm.DB @@ -237,10 +262,20 @@ func (a characterSubjectsHasOneSubject) WithContext(ctx context.Context) *charac return &a } +func (a characterSubjectsHasOneSubject) Session(session *gorm.Session) *characterSubjectsHasOneSubject { + a.db = a.db.Session(session) + return &a +} + func (a characterSubjectsHasOneSubject) Model(m *dao.CharacterSubjects) *characterSubjectsHasOneSubjectTx { return &characterSubjectsHasOneSubjectTx{a.db.Model(m).Association(a.Name())} } +func (a characterSubjectsHasOneSubject) Unscoped() *characterSubjectsHasOneSubject { + a.db = a.db.Unscoped() + return &a +} + type characterSubjectsHasOneSubjectTx struct{ tx *gorm.Association } func (a characterSubjectsHasOneSubjectTx) Find() (result *dao.Subject, err error) { @@ -279,6 +314,11 @@ func (a characterSubjectsHasOneSubjectTx) Count() int64 { return a.tx.Count() } +func (a characterSubjectsHasOneSubjectTx) Unscoped() *characterSubjectsHasOneSubjectTx { + a.tx = a.tx.Unscoped() + return &a +} + type characterSubjectsDo struct{ gen.DO } func (c characterSubjectsDo) Debug() *characterSubjectsDo { @@ -325,10 +365,6 @@ func (c characterSubjectsDo) Where(conds ...gen.Condition) *characterSubjectsDo return c.withDO(c.DO.Where(conds...)) } -func (c characterSubjectsDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *characterSubjectsDo { - return c.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (c characterSubjectsDo) Order(conds ...field.Expr) *characterSubjectsDo { return c.withDO(c.DO.Order(conds...)) } diff --git a/dal/query/chii_ep_status.gen.go b/dal/query/chii_ep_status.gen.go index bb3e88ca2..ca7a76a11 100644 --- a/dal/query/chii_ep_status.gen.go +++ b/dal/query/chii_ep_status.gen.go @@ -85,6 +85,10 @@ func (e epCollection) TableName() string { return e.epCollectionDo.TableName() } func (e epCollection) Alias() string { return e.epCollectionDo.Alias() } +func (e epCollection) Columns(cols ...field.Expr) gen.Columns { + return e.epCollectionDo.Columns(cols...) +} + func (e *epCollection) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := e.fieldMap[fieldName] if !ok || _f == nil { @@ -160,10 +164,6 @@ func (e epCollectionDo) Where(conds ...gen.Condition) *epCollectionDo { return e.withDO(e.DO.Where(conds...)) } -func (e epCollectionDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *epCollectionDo { - return e.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (e epCollectionDo) Order(conds ...field.Expr) *epCollectionDo { return e.withDO(e.DO.Order(conds...)) } diff --git a/dal/query/chii_episodes.gen.go b/dal/query/chii_episodes.gen.go index aec6d589c..04cfb6e9d 100644 --- a/dal/query/chii_episodes.gen.go +++ b/dal/query/chii_episodes.gen.go @@ -130,6 +130,8 @@ func (e episode) TableName() string { return e.episodeDo.TableName() } func (e episode) Alias() string { return e.episodeDo.Alias() } +func (e episode) Columns(cols ...field.Expr) gen.Columns { return e.episodeDo.Columns(cols...) } + func (e *episode) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := e.fieldMap[fieldName] if !ok || _f == nil { @@ -164,11 +166,14 @@ func (e *episode) fillFieldMap() { func (e episode) clone(db *gorm.DB) episode { e.episodeDo.ReplaceConnPool(db.Statement.ConnPool) + e.Subject.db = db.Session(&gorm.Session{Initialized: true}) + e.Subject.db.Statement.ConnPool = db.Statement.ConnPool return e } func (e episode) replaceDB(db *gorm.DB) episode { e.episodeDo.ReplaceDB(db) + e.Subject.db = db.Session(&gorm.Session{}) return e } @@ -200,10 +205,20 @@ func (a episodeBelongsToSubject) WithContext(ctx context.Context) *episodeBelong return &a } +func (a episodeBelongsToSubject) Session(session *gorm.Session) *episodeBelongsToSubject { + a.db = a.db.Session(session) + return &a +} + func (a episodeBelongsToSubject) Model(m *dao.Episode) *episodeBelongsToSubjectTx { return &episodeBelongsToSubjectTx{a.db.Model(m).Association(a.Name())} } +func (a episodeBelongsToSubject) Unscoped() *episodeBelongsToSubject { + a.db = a.db.Unscoped() + return &a +} + type episodeBelongsToSubjectTx struct{ tx *gorm.Association } func (a episodeBelongsToSubjectTx) Find() (result *dao.Subject, err error) { @@ -242,6 +257,11 @@ func (a episodeBelongsToSubjectTx) Count() int64 { return a.tx.Count() } +func (a episodeBelongsToSubjectTx) Unscoped() *episodeBelongsToSubjectTx { + a.tx = a.tx.Unscoped() + return &a +} + type episodeDo struct{ gen.DO } func (e episodeDo) Debug() *episodeDo { @@ -288,10 +308,6 @@ func (e episodeDo) Where(conds ...gen.Condition) *episodeDo { return e.withDO(e.DO.Where(conds...)) } -func (e episodeDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *episodeDo { - return e.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (e episodeDo) Order(conds ...field.Expr) *episodeDo { return e.withDO(e.DO.Order(conds...)) } diff --git a/dal/query/chii_friends.gen.go b/dal/query/chii_friends.gen.go index 821f7ae80..331d4aa70 100644 --- a/dal/query/chii_friends.gen.go +++ b/dal/query/chii_friends.gen.go @@ -80,6 +80,8 @@ func (f friend) TableName() string { return f.friendDo.TableName() } func (f friend) Alias() string { return f.friendDo.Alias() } +func (f friend) Columns(cols ...field.Expr) gen.Columns { return f.friendDo.Columns(cols...) } + func (f *friend) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := f.fieldMap[fieldName] if !ok || _f == nil { @@ -154,10 +156,6 @@ func (f friendDo) Where(conds ...gen.Condition) *friendDo { return f.withDO(f.DO.Where(conds...)) } -func (f friendDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *friendDo { - return f.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (f friendDo) Order(conds ...field.Expr) *friendDo { return f.withDO(f.DO.Order(conds...)) } diff --git a/dal/query/chii_index.gen.go b/dal/query/chii_index.gen.go index 6745d1fab..0f8d7abdf 100644 --- a/dal/query/chii_index.gen.go +++ b/dal/query/chii_index.gen.go @@ -35,10 +35,11 @@ func newIndex(db *gorm.DB, opts ...gen.DOOption) index { _index.SubjectCount = field.NewUint32(tableName, "idx_subject_total") _index.CollectCount = field.NewUint32(tableName, "idx_collects") _index.Stats = field.NewString(tableName, "idx_stats") + _index.Award = field.NewUint32(tableName, "idx_award") _index.CreatedTime = field.NewInt32(tableName, "idx_dateline") _index.UpdatedTime = field.NewUint32(tableName, "idx_lasttouch") _index.CreatorID = field.NewUint32(tableName, "idx_uid") - _index.Ban = field.NewBool(tableName, "idx_ban") + _index.Privacy = field.NewUint8(tableName, "idx_ban") _index.fillFieldMap() @@ -57,10 +58,11 @@ type index struct { SubjectCount field.Uint32 // 内含条目总数 CollectCount field.Uint32 // 收藏数 Stats field.String + Award field.Uint32 CreatedTime field.Int32 // 创建时间 UpdatedTime field.Uint32 CreatorID field.Uint32 // 创建人UID - Ban field.Bool + Privacy field.Uint8 fieldMap map[string]field.Expr } @@ -85,10 +87,11 @@ func (i *index) updateTableName(table string) *index { i.SubjectCount = field.NewUint32(table, "idx_subject_total") i.CollectCount = field.NewUint32(table, "idx_collects") i.Stats = field.NewString(table, "idx_stats") + i.Award = field.NewUint32(table, "idx_award") i.CreatedTime = field.NewInt32(table, "idx_dateline") i.UpdatedTime = field.NewUint32(table, "idx_lasttouch") i.CreatorID = field.NewUint32(table, "idx_uid") - i.Ban = field.NewBool(table, "idx_ban") + i.Privacy = field.NewUint8(table, "idx_ban") i.fillFieldMap() @@ -101,6 +104,8 @@ func (i index) TableName() string { return i.indexDo.TableName() } func (i index) Alias() string { return i.indexDo.Alias() } +func (i index) Columns(cols ...field.Expr) gen.Columns { return i.indexDo.Columns(cols...) } + func (i *index) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := i.fieldMap[fieldName] if !ok || _f == nil { @@ -111,7 +116,7 @@ func (i *index) GetFieldByName(fieldName string) (field.OrderExpr, bool) { } func (i *index) fillFieldMap() { - i.fieldMap = make(map[string]field.Expr, 12) + i.fieldMap = make(map[string]field.Expr, 13) i.fieldMap["idx_id"] = i.ID i.fieldMap["idx_type"] = i.Type i.fieldMap["idx_title"] = i.Title @@ -120,10 +125,11 @@ func (i *index) fillFieldMap() { i.fieldMap["idx_subject_total"] = i.SubjectCount i.fieldMap["idx_collects"] = i.CollectCount i.fieldMap["idx_stats"] = i.Stats + i.fieldMap["idx_award"] = i.Award i.fieldMap["idx_dateline"] = i.CreatedTime i.fieldMap["idx_lasttouch"] = i.UpdatedTime i.fieldMap["idx_uid"] = i.CreatorID - i.fieldMap["idx_ban"] = i.Ban + i.fieldMap["idx_ban"] = i.Privacy } func (i index) clone(db *gorm.DB) index { @@ -182,10 +188,6 @@ func (i indexDo) Where(conds ...gen.Condition) *indexDo { return i.withDO(i.DO.Where(conds...)) } -func (i indexDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *indexDo { - return i.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (i indexDo) Order(conds ...field.Expr) *indexDo { return i.withDO(i.DO.Order(conds...)) } diff --git a/dal/query/chii_index_collects.gen.go b/dal/query/chii_index_collects.gen.go index 1fbc96a97..bbf4802c9 100644 --- a/dal/query/chii_index_collects.gen.go +++ b/dal/query/chii_index_collects.gen.go @@ -37,6 +37,7 @@ func newIndexCollect(db *gorm.DB, opts ...gen.DOOption) indexCollect { return _indexCollect } +// indexCollect 目录收藏 type indexCollect struct { indexCollectDo indexCollectDo @@ -79,6 +80,10 @@ func (i indexCollect) TableName() string { return i.indexCollectDo.TableName() } func (i indexCollect) Alias() string { return i.indexCollectDo.Alias() } +func (i indexCollect) Columns(cols ...field.Expr) gen.Columns { + return i.indexCollectDo.Columns(cols...) +} + func (i *indexCollect) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := i.fieldMap[fieldName] if !ok || _f == nil { @@ -152,10 +157,6 @@ func (i indexCollectDo) Where(conds ...gen.Condition) *indexCollectDo { return i.withDO(i.DO.Where(conds...)) } -func (i indexCollectDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *indexCollectDo { - return i.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (i indexCollectDo) Order(conds ...field.Expr) *indexCollectDo { return i.withDO(i.DO.Order(conds...)) } diff --git a/dal/query/chii_index_related.gen.go b/dal/query/chii_index_related.gen.go index 088ba04f2..9d3c115fd 100644 --- a/dal/query/chii_index_related.gen.go +++ b/dal/query/chii_index_related.gen.go @@ -33,8 +33,10 @@ func newIndexSubject(db *gorm.DB, opts ...gen.DOOption) indexSubject { _indexSubject.SubjectType = field.NewUint8(tableName, "idx_rlt_type") _indexSubject.SubjectID = field.NewUint32(tableName, "idx_rlt_sid") _indexSubject.Order = field.NewUint32(tableName, "idx_rlt_order") + _indexSubject.Award = field.NewString(tableName, "idx_rlt_award") _indexSubject.Comment = field.NewString(tableName, "idx_rlt_comment") _indexSubject.CreatedTime = field.NewUint32(tableName, "idx_rlt_dateline") + _indexSubject.Deleted = field.NewField(tableName, "idx_rlt_ban") _indexSubject.Subject = indexSubjectBelongsToSubject{ db: db.Session(&gorm.Session{}), @@ -51,6 +53,7 @@ func newIndexSubject(db *gorm.DB, opts ...gen.DOOption) indexSubject { return _indexSubject } +// indexSubject 目录关联表 type indexSubject struct { indexSubjectDo indexSubjectDo @@ -61,8 +64,10 @@ type indexSubject struct { SubjectType field.Uint8 // 关联条目类型 SubjectID field.Uint32 // 关联条目ID Order field.Uint32 + Award field.String Comment field.String CreatedTime field.Uint32 + Deleted field.Field Subject indexSubjectBelongsToSubject fieldMap map[string]field.Expr @@ -86,8 +91,10 @@ func (i *indexSubject) updateTableName(table string) *indexSubject { i.SubjectType = field.NewUint8(table, "idx_rlt_type") i.SubjectID = field.NewUint32(table, "idx_rlt_sid") i.Order = field.NewUint32(table, "idx_rlt_order") + i.Award = field.NewString(table, "idx_rlt_award") i.Comment = field.NewString(table, "idx_rlt_comment") i.CreatedTime = field.NewUint32(table, "idx_rlt_dateline") + i.Deleted = field.NewField(table, "idx_rlt_ban") i.fillFieldMap() @@ -102,6 +109,10 @@ func (i indexSubject) TableName() string { return i.indexSubjectDo.TableName() } func (i indexSubject) Alias() string { return i.indexSubjectDo.Alias() } +func (i indexSubject) Columns(cols ...field.Expr) gen.Columns { + return i.indexSubjectDo.Columns(cols...) +} + func (i *indexSubject) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := i.fieldMap[fieldName] if !ok || _f == nil { @@ -112,25 +123,30 @@ func (i *indexSubject) GetFieldByName(fieldName string) (field.OrderExpr, bool) } func (i *indexSubject) fillFieldMap() { - i.fieldMap = make(map[string]field.Expr, 9) + i.fieldMap = make(map[string]field.Expr, 11) i.fieldMap["idx_rlt_id"] = i.ID i.fieldMap["idx_rlt_cat"] = i.Cat i.fieldMap["idx_rlt_rid"] = i.IndexID i.fieldMap["idx_rlt_type"] = i.SubjectType i.fieldMap["idx_rlt_sid"] = i.SubjectID i.fieldMap["idx_rlt_order"] = i.Order + i.fieldMap["idx_rlt_award"] = i.Award i.fieldMap["idx_rlt_comment"] = i.Comment i.fieldMap["idx_rlt_dateline"] = i.CreatedTime + i.fieldMap["idx_rlt_ban"] = i.Deleted } func (i indexSubject) clone(db *gorm.DB) indexSubject { i.indexSubjectDo.ReplaceConnPool(db.Statement.ConnPool) + i.Subject.db = db.Session(&gorm.Session{Initialized: true}) + i.Subject.db.Statement.ConnPool = db.Statement.ConnPool return i } func (i indexSubject) replaceDB(db *gorm.DB) indexSubject { i.indexSubjectDo.ReplaceDB(db) + i.Subject.db = db.Session(&gorm.Session{}) return i } @@ -162,10 +178,20 @@ func (a indexSubjectBelongsToSubject) WithContext(ctx context.Context) *indexSub return &a } +func (a indexSubjectBelongsToSubject) Session(session *gorm.Session) *indexSubjectBelongsToSubject { + a.db = a.db.Session(session) + return &a +} + func (a indexSubjectBelongsToSubject) Model(m *dao.IndexSubject) *indexSubjectBelongsToSubjectTx { return &indexSubjectBelongsToSubjectTx{a.db.Model(m).Association(a.Name())} } +func (a indexSubjectBelongsToSubject) Unscoped() *indexSubjectBelongsToSubject { + a.db = a.db.Unscoped() + return &a +} + type indexSubjectBelongsToSubjectTx struct{ tx *gorm.Association } func (a indexSubjectBelongsToSubjectTx) Find() (result *dao.Subject, err error) { @@ -204,6 +230,11 @@ func (a indexSubjectBelongsToSubjectTx) Count() int64 { return a.tx.Count() } +func (a indexSubjectBelongsToSubjectTx) Unscoped() *indexSubjectBelongsToSubjectTx { + a.tx = a.tx.Unscoped() + return &a +} + type indexSubjectDo struct{ gen.DO } func (i indexSubjectDo) Debug() *indexSubjectDo { @@ -250,10 +281,6 @@ func (i indexSubjectDo) Where(conds ...gen.Condition) *indexSubjectDo { return i.withDO(i.DO.Where(conds...)) } -func (i indexSubjectDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *indexSubjectDo { - return i.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (i indexSubjectDo) Order(conds ...field.Expr) *indexSubjectDo { return i.withDO(i.DO.Order(conds...)) } diff --git a/dal/query/chii_members.gen.go b/dal/query/chii_members.gen.go index 417b3a295..8de03832d 100644 --- a/dal/query/chii_members.gen.go +++ b/dal/query/chii_members.gen.go @@ -44,6 +44,7 @@ func newMember(db *gorm.DB, opts ...gen.DOOption) member { _member.Sign = field.NewField(tableName, "sign") _member.PasswordCrypt = field.NewBytes(tableName, "password_crypt") _member.Email = field.NewString(tableName, "email") + _member.Acl = field.NewString(tableName, "acl") _member.Fields = memberHasOneFields{ db: db.Session(&gorm.Session{}), @@ -76,6 +77,7 @@ type member struct { Sign field.Field PasswordCrypt field.Bytes Email field.String + Acl field.String Fields memberHasOneFields fieldMap map[string]field.Expr @@ -110,6 +112,7 @@ func (m *member) updateTableName(table string) *member { m.Sign = field.NewField(table, "sign") m.PasswordCrypt = field.NewBytes(table, "password_crypt") m.Email = field.NewString(table, "email") + m.Acl = field.NewString(table, "acl") m.fillFieldMap() @@ -122,6 +125,8 @@ func (m member) TableName() string { return m.memberDo.TableName() } func (m member) Alias() string { return m.memberDo.Alias() } +func (m member) Columns(cols ...field.Expr) gen.Columns { return m.memberDo.Columns(cols...) } + func (m *member) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := m.fieldMap[fieldName] if !ok || _f == nil { @@ -132,7 +137,7 @@ func (m *member) GetFieldByName(fieldName string) (field.OrderExpr, bool) { } func (m *member) fillFieldMap() { - m.fieldMap = make(map[string]field.Expr, 18) + m.fieldMap = make(map[string]field.Expr, 19) m.fieldMap["uid"] = m.ID m.fieldMap["username"] = m.Username m.fieldMap["nickname"] = m.Nickname @@ -150,16 +155,20 @@ func (m *member) fillFieldMap() { m.fieldMap["sign"] = m.Sign m.fieldMap["password_crypt"] = m.PasswordCrypt m.fieldMap["email"] = m.Email + m.fieldMap["acl"] = m.Acl } func (m member) clone(db *gorm.DB) member { m.memberDo.ReplaceConnPool(db.Statement.ConnPool) + m.Fields.db = db.Session(&gorm.Session{Initialized: true}) + m.Fields.db.Statement.ConnPool = db.Statement.ConnPool return m } func (m member) replaceDB(db *gorm.DB) member { m.memberDo.ReplaceDB(db) + m.Fields.db = db.Session(&gorm.Session{}) return m } @@ -187,10 +196,20 @@ func (a memberHasOneFields) WithContext(ctx context.Context) *memberHasOneFields return &a } +func (a memberHasOneFields) Session(session *gorm.Session) *memberHasOneFields { + a.db = a.db.Session(session) + return &a +} + func (a memberHasOneFields) Model(m *dao.Member) *memberHasOneFieldsTx { return &memberHasOneFieldsTx{a.db.Model(m).Association(a.Name())} } +func (a memberHasOneFields) Unscoped() *memberHasOneFields { + a.db = a.db.Unscoped() + return &a +} + type memberHasOneFieldsTx struct{ tx *gorm.Association } func (a memberHasOneFieldsTx) Find() (result *dao.MemberField, err error) { @@ -229,6 +248,11 @@ func (a memberHasOneFieldsTx) Count() int64 { return a.tx.Count() } +func (a memberHasOneFieldsTx) Unscoped() *memberHasOneFieldsTx { + a.tx = a.tx.Unscoped() + return &a +} + type memberDo struct{ gen.DO } func (m memberDo) Debug() *memberDo { @@ -275,10 +299,6 @@ func (m memberDo) Where(conds ...gen.Condition) *memberDo { return m.withDO(m.DO.Where(conds...)) } -func (m memberDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *memberDo { - return m.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (m memberDo) Order(conds ...field.Expr) *memberDo { return m.withDO(m.DO.Order(conds...)) } diff --git a/dal/query/chii_notify.gen.go b/dal/query/chii_notify.gen.go index 2a917c086..11273b50a 100644 --- a/dal/query/chii_notify.gen.go +++ b/dal/query/chii_notify.gen.go @@ -91,6 +91,10 @@ func (n notification) TableName() string { return n.notificationDo.TableName() } func (n notification) Alias() string { return n.notificationDo.Alias() } +func (n notification) Columns(cols ...field.Expr) gen.Columns { + return n.notificationDo.Columns(cols...) +} + func (n *notification) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := n.fieldMap[fieldName] if !ok || _f == nil { @@ -168,10 +172,6 @@ func (n notificationDo) Where(conds ...gen.Condition) *notificationDo { return n.withDO(n.DO.Where(conds...)) } -func (n notificationDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *notificationDo { - return n.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (n notificationDo) Order(conds ...field.Expr) *notificationDo { return n.withDO(n.DO.Order(conds...)) } diff --git a/dal/query/chii_notify_field.gen.go b/dal/query/chii_notify_field.gen.go index 4ef2b8316..363945ce1 100644 --- a/dal/query/chii_notify_field.gen.go +++ b/dal/query/chii_notify_field.gen.go @@ -79,6 +79,10 @@ func (n notificationField) TableName() string { return n.notificationFieldDo.Tab func (n notificationField) Alias() string { return n.notificationFieldDo.Alias() } +func (n notificationField) Columns(cols ...field.Expr) gen.Columns { + return n.notificationFieldDo.Columns(cols...) +} + func (n *notificationField) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := n.fieldMap[fieldName] if !ok || _f == nil { @@ -152,10 +156,6 @@ func (n notificationFieldDo) Where(conds ...gen.Condition) *notificationFieldDo return n.withDO(n.DO.Where(conds...)) } -func (n notificationFieldDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *notificationFieldDo { - return n.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (n notificationFieldDo) Order(conds ...field.Expr) *notificationFieldDo { return n.withDO(n.DO.Order(conds...)) } diff --git a/dal/query/chii_oauth_access_tokens.gen.go b/dal/query/chii_oauth_access_tokens.gen.go index d5b0e8226..803aa502e 100644 --- a/dal/query/chii_oauth_access_tokens.gen.go +++ b/dal/query/chii_oauth_access_tokens.gen.go @@ -91,6 +91,8 @@ func (a accessToken) TableName() string { return a.accessTokenDo.TableName() } func (a accessToken) Alias() string { return a.accessTokenDo.Alias() } +func (a accessToken) Columns(cols ...field.Expr) gen.Columns { return a.accessTokenDo.Columns(cols...) } + func (a *accessToken) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := a.fieldMap[fieldName] if !ok || _f == nil { @@ -168,10 +170,6 @@ func (a accessTokenDo) Where(conds ...gen.Condition) *accessTokenDo { return a.withDO(a.DO.Where(conds...)) } -func (a accessTokenDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *accessTokenDo { - return a.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (a accessTokenDo) Order(conds ...field.Expr) *accessTokenDo { return a.withDO(a.DO.Order(conds...)) } diff --git a/dal/query/chii_os_web_sessions.gen.go b/dal/query/chii_os_web_sessions.gen.go index 962a47554..7c43b1c17 100644 --- a/dal/query/chii_os_web_sessions.gen.go +++ b/dal/query/chii_os_web_sessions.gen.go @@ -82,6 +82,8 @@ func (w webSession) TableName() string { return w.webSessionDo.TableName() } func (w webSession) Alias() string { return w.webSessionDo.Alias() } +func (w webSession) Columns(cols ...field.Expr) gen.Columns { return w.webSessionDo.Columns(cols...) } + func (w *webSession) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := w.fieldMap[fieldName] if !ok || _f == nil { @@ -156,10 +158,6 @@ func (w webSessionDo) Where(conds ...gen.Condition) *webSessionDo { return w.withDO(w.DO.Where(conds...)) } -func (w webSessionDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *webSessionDo { - return w.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (w webSessionDo) Order(conds ...field.Expr) *webSessionDo { return w.withDO(w.DO.Order(conds...)) } diff --git a/dal/query/chii_person_collects.gen.go b/dal/query/chii_person_collects.gen.go new file mode 100644 index 000000000..bd7753700 --- /dev/null +++ b/dal/query/chii_person_collects.gen.go @@ -0,0 +1,348 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package query + +import ( + "context" + + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + + "gorm.io/gen" + "gorm.io/gen/field" + + "gorm.io/plugin/dbresolver" + + "github.com/bangumi/server/dal/dao" +) + +func newPersonCollect(db *gorm.DB, opts ...gen.DOOption) personCollect { + _personCollect := personCollect{} + + _personCollect.personCollectDo.UseDB(db, opts...) + _personCollect.personCollectDo.UseModel(&dao.PersonCollect{}) + + tableName := _personCollect.personCollectDo.TableName() + _personCollect.ALL = field.NewAsterisk(tableName) + _personCollect.ID = field.NewUint32(tableName, "prsn_clt_id") + _personCollect.Category = field.NewString(tableName, "prsn_clt_cat") + _personCollect.TargetID = field.NewUint32(tableName, "prsn_clt_mid") + _personCollect.UserID = field.NewUint32(tableName, "prsn_clt_uid") + _personCollect.CreatedTime = field.NewUint32(tableName, "prsn_clt_dateline") + + _personCollect.fillFieldMap() + + return _personCollect +} + +// personCollect 人物收藏 +type personCollect struct { + personCollectDo personCollectDo + + ALL field.Asterisk + ID field.Uint32 + Category field.String + TargetID field.Uint32 + UserID field.Uint32 + CreatedTime field.Uint32 + + fieldMap map[string]field.Expr +} + +func (p personCollect) Table(newTableName string) *personCollect { + p.personCollectDo.UseTable(newTableName) + return p.updateTableName(newTableName) +} + +func (p personCollect) As(alias string) *personCollect { + p.personCollectDo.DO = *(p.personCollectDo.As(alias).(*gen.DO)) + return p.updateTableName(alias) +} + +func (p *personCollect) updateTableName(table string) *personCollect { + p.ALL = field.NewAsterisk(table) + p.ID = field.NewUint32(table, "prsn_clt_id") + p.Category = field.NewString(table, "prsn_clt_cat") + p.TargetID = field.NewUint32(table, "prsn_clt_mid") + p.UserID = field.NewUint32(table, "prsn_clt_uid") + p.CreatedTime = field.NewUint32(table, "prsn_clt_dateline") + + p.fillFieldMap() + + return p +} + +func (p *personCollect) WithContext(ctx context.Context) *personCollectDo { + return p.personCollectDo.WithContext(ctx) +} + +func (p personCollect) TableName() string { return p.personCollectDo.TableName() } + +func (p personCollect) Alias() string { return p.personCollectDo.Alias() } + +func (p personCollect) Columns(cols ...field.Expr) gen.Columns { + return p.personCollectDo.Columns(cols...) +} + +func (p *personCollect) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := p.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (p *personCollect) fillFieldMap() { + p.fieldMap = make(map[string]field.Expr, 5) + p.fieldMap["prsn_clt_id"] = p.ID + p.fieldMap["prsn_clt_cat"] = p.Category + p.fieldMap["prsn_clt_mid"] = p.TargetID + p.fieldMap["prsn_clt_uid"] = p.UserID + p.fieldMap["prsn_clt_dateline"] = p.CreatedTime +} + +func (p personCollect) clone(db *gorm.DB) personCollect { + p.personCollectDo.ReplaceConnPool(db.Statement.ConnPool) + return p +} + +func (p personCollect) replaceDB(db *gorm.DB) personCollect { + p.personCollectDo.ReplaceDB(db) + return p +} + +type personCollectDo struct{ gen.DO } + +func (p personCollectDo) Debug() *personCollectDo { + return p.withDO(p.DO.Debug()) +} + +func (p personCollectDo) WithContext(ctx context.Context) *personCollectDo { + return p.withDO(p.DO.WithContext(ctx)) +} + +func (p personCollectDo) ReadDB() *personCollectDo { + return p.Clauses(dbresolver.Read) +} + +func (p personCollectDo) WriteDB() *personCollectDo { + return p.Clauses(dbresolver.Write) +} + +func (p personCollectDo) Session(config *gorm.Session) *personCollectDo { + return p.withDO(p.DO.Session(config)) +} + +func (p personCollectDo) Clauses(conds ...clause.Expression) *personCollectDo { + return p.withDO(p.DO.Clauses(conds...)) +} + +func (p personCollectDo) Returning(value interface{}, columns ...string) *personCollectDo { + return p.withDO(p.DO.Returning(value, columns...)) +} + +func (p personCollectDo) Not(conds ...gen.Condition) *personCollectDo { + return p.withDO(p.DO.Not(conds...)) +} + +func (p personCollectDo) Or(conds ...gen.Condition) *personCollectDo { + return p.withDO(p.DO.Or(conds...)) +} + +func (p personCollectDo) Select(conds ...field.Expr) *personCollectDo { + return p.withDO(p.DO.Select(conds...)) +} + +func (p personCollectDo) Where(conds ...gen.Condition) *personCollectDo { + return p.withDO(p.DO.Where(conds...)) +} + +func (p personCollectDo) Order(conds ...field.Expr) *personCollectDo { + return p.withDO(p.DO.Order(conds...)) +} + +func (p personCollectDo) Distinct(cols ...field.Expr) *personCollectDo { + return p.withDO(p.DO.Distinct(cols...)) +} + +func (p personCollectDo) Omit(cols ...field.Expr) *personCollectDo { + return p.withDO(p.DO.Omit(cols...)) +} + +func (p personCollectDo) Join(table schema.Tabler, on ...field.Expr) *personCollectDo { + return p.withDO(p.DO.Join(table, on...)) +} + +func (p personCollectDo) LeftJoin(table schema.Tabler, on ...field.Expr) *personCollectDo { + return p.withDO(p.DO.LeftJoin(table, on...)) +} + +func (p personCollectDo) RightJoin(table schema.Tabler, on ...field.Expr) *personCollectDo { + return p.withDO(p.DO.RightJoin(table, on...)) +} + +func (p personCollectDo) Group(cols ...field.Expr) *personCollectDo { + return p.withDO(p.DO.Group(cols...)) +} + +func (p personCollectDo) Having(conds ...gen.Condition) *personCollectDo { + return p.withDO(p.DO.Having(conds...)) +} + +func (p personCollectDo) Limit(limit int) *personCollectDo { + return p.withDO(p.DO.Limit(limit)) +} + +func (p personCollectDo) Offset(offset int) *personCollectDo { + return p.withDO(p.DO.Offset(offset)) +} + +func (p personCollectDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *personCollectDo { + return p.withDO(p.DO.Scopes(funcs...)) +} + +func (p personCollectDo) Unscoped() *personCollectDo { + return p.withDO(p.DO.Unscoped()) +} + +func (p personCollectDo) Create(values ...*dao.PersonCollect) error { + if len(values) == 0 { + return nil + } + return p.DO.Create(values) +} + +func (p personCollectDo) CreateInBatches(values []*dao.PersonCollect, batchSize int) error { + return p.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (p personCollectDo) Save(values ...*dao.PersonCollect) error { + if len(values) == 0 { + return nil + } + return p.DO.Save(values) +} + +func (p personCollectDo) First() (*dao.PersonCollect, error) { + if result, err := p.DO.First(); err != nil { + return nil, err + } else { + return result.(*dao.PersonCollect), nil + } +} + +func (p personCollectDo) Take() (*dao.PersonCollect, error) { + if result, err := p.DO.Take(); err != nil { + return nil, err + } else { + return result.(*dao.PersonCollect), nil + } +} + +func (p personCollectDo) Last() (*dao.PersonCollect, error) { + if result, err := p.DO.Last(); err != nil { + return nil, err + } else { + return result.(*dao.PersonCollect), nil + } +} + +func (p personCollectDo) Find() ([]*dao.PersonCollect, error) { + result, err := p.DO.Find() + return result.([]*dao.PersonCollect), err +} + +func (p personCollectDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*dao.PersonCollect, err error) { + buf := make([]*dao.PersonCollect, 0, batchSize) + err = p.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (p personCollectDo) FindInBatches(result *[]*dao.PersonCollect, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return p.DO.FindInBatches(result, batchSize, fc) +} + +func (p personCollectDo) Attrs(attrs ...field.AssignExpr) *personCollectDo { + return p.withDO(p.DO.Attrs(attrs...)) +} + +func (p personCollectDo) Assign(attrs ...field.AssignExpr) *personCollectDo { + return p.withDO(p.DO.Assign(attrs...)) +} + +func (p personCollectDo) Joins(fields ...field.RelationField) *personCollectDo { + for _, _f := range fields { + p = *p.withDO(p.DO.Joins(_f)) + } + return &p +} + +func (p personCollectDo) Preload(fields ...field.RelationField) *personCollectDo { + for _, _f := range fields { + p = *p.withDO(p.DO.Preload(_f)) + } + return &p +} + +func (p personCollectDo) FirstOrInit() (*dao.PersonCollect, error) { + if result, err := p.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*dao.PersonCollect), nil + } +} + +func (p personCollectDo) FirstOrCreate() (*dao.PersonCollect, error) { + if result, err := p.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*dao.PersonCollect), nil + } +} + +func (p personCollectDo) FindByPage(offset int, limit int) (result []*dao.PersonCollect, count int64, err error) { + result, err = p.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = p.Offset(-1).Limit(-1).Count() + return +} + +func (p personCollectDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = p.Count() + if err != nil { + return + } + + err = p.Offset(offset).Limit(limit).Scan(result) + return +} + +func (p personCollectDo) Scan(result interface{}) (err error) { + return p.DO.Scan(result) +} + +func (p personCollectDo) Delete(models ...*dao.PersonCollect) (result gen.ResultInfo, err error) { + return p.DO.Delete(models) +} + +func (p *personCollectDo) withDO(do gen.Dao) *personCollectDo { + p.DO = *do.(*gen.DO) + return p +} diff --git a/dal/query/chii_person_cs_index.gen.go b/dal/query/chii_person_cs_index.gen.go index 778c1811c..d1e22a02b 100644 --- a/dal/query/chii_person_cs_index.gen.go +++ b/dal/query/chii_person_cs_index.gen.go @@ -61,6 +61,7 @@ func newPersonSubjects(db *gorm.DB, opts ...gen.DOOption) personSubjects { return _personSubjects } +// personSubjects subjects' credits/creator & staff (c&s)index type personSubjects struct { personSubjectsDo personSubjectsDo @@ -112,6 +113,10 @@ func (p personSubjects) TableName() string { return p.personSubjectsDo.TableName func (p personSubjects) Alias() string { return p.personSubjectsDo.Alias() } +func (p personSubjects) Columns(cols ...field.Expr) gen.Columns { + return p.personSubjectsDo.Columns(cols...) +} + func (p *personSubjects) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := p.fieldMap[fieldName] if !ok || _f == nil { @@ -135,11 +140,17 @@ func (p *personSubjects) fillFieldMap() { func (p personSubjects) clone(db *gorm.DB) personSubjects { p.personSubjectsDo.ReplaceConnPool(db.Statement.ConnPool) + p.Subject.db = db.Session(&gorm.Session{Initialized: true}) + p.Subject.db.Statement.ConnPool = db.Statement.ConnPool + p.Person.db = db.Session(&gorm.Session{Initialized: true}) + p.Person.db.Statement.ConnPool = db.Statement.ConnPool return p } func (p personSubjects) replaceDB(db *gorm.DB) personSubjects { p.personSubjectsDo.ReplaceDB(db) + p.Subject.db = db.Session(&gorm.Session{}) + p.Person.db = db.Session(&gorm.Session{}) return p } @@ -171,10 +182,20 @@ func (a personSubjectsHasOneSubject) WithContext(ctx context.Context) *personSub return &a } +func (a personSubjectsHasOneSubject) Session(session *gorm.Session) *personSubjectsHasOneSubject { + a.db = a.db.Session(session) + return &a +} + func (a personSubjectsHasOneSubject) Model(m *dao.PersonSubjects) *personSubjectsHasOneSubjectTx { return &personSubjectsHasOneSubjectTx{a.db.Model(m).Association(a.Name())} } +func (a personSubjectsHasOneSubject) Unscoped() *personSubjectsHasOneSubject { + a.db = a.db.Unscoped() + return &a +} + type personSubjectsHasOneSubjectTx struct{ tx *gorm.Association } func (a personSubjectsHasOneSubjectTx) Find() (result *dao.Subject, err error) { @@ -213,6 +234,11 @@ func (a personSubjectsHasOneSubjectTx) Count() int64 { return a.tx.Count() } +func (a personSubjectsHasOneSubjectTx) Unscoped() *personSubjectsHasOneSubjectTx { + a.tx = a.tx.Unscoped() + return &a +} + type personSubjectsHasOnePerson struct { db *gorm.DB @@ -241,10 +267,20 @@ func (a personSubjectsHasOnePerson) WithContext(ctx context.Context) *personSubj return &a } +func (a personSubjectsHasOnePerson) Session(session *gorm.Session) *personSubjectsHasOnePerson { + a.db = a.db.Session(session) + return &a +} + func (a personSubjectsHasOnePerson) Model(m *dao.PersonSubjects) *personSubjectsHasOnePersonTx { return &personSubjectsHasOnePersonTx{a.db.Model(m).Association(a.Name())} } +func (a personSubjectsHasOnePerson) Unscoped() *personSubjectsHasOnePerson { + a.db = a.db.Unscoped() + return &a +} + type personSubjectsHasOnePersonTx struct{ tx *gorm.Association } func (a personSubjectsHasOnePersonTx) Find() (result *dao.Person, err error) { @@ -283,6 +319,11 @@ func (a personSubjectsHasOnePersonTx) Count() int64 { return a.tx.Count() } +func (a personSubjectsHasOnePersonTx) Unscoped() *personSubjectsHasOnePersonTx { + a.tx = a.tx.Unscoped() + return &a +} + type personSubjectsDo struct{ gen.DO } func (p personSubjectsDo) Debug() *personSubjectsDo { @@ -329,10 +370,6 @@ func (p personSubjectsDo) Where(conds ...gen.Condition) *personSubjectsDo { return p.withDO(p.DO.Where(conds...)) } -func (p personSubjectsDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *personSubjectsDo { - return p.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (p personSubjectsDo) Order(conds ...field.Expr) *personSubjectsDo { return p.withDO(p.DO.Order(conds...)) } diff --git a/dal/query/chii_person_fields.gen.go b/dal/query/chii_person_fields.gen.go index cd4330673..deb5853aa 100644 --- a/dal/query/chii_person_fields.gen.go +++ b/dal/query/chii_person_fields.gen.go @@ -88,6 +88,8 @@ func (p personField) TableName() string { return p.personFieldDo.TableName() } func (p personField) Alias() string { return p.personFieldDo.Alias() } +func (p personField) Columns(cols ...field.Expr) gen.Columns { return p.personFieldDo.Columns(cols...) } + func (p *personField) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := p.fieldMap[fieldName] if !ok || _f == nil { @@ -164,10 +166,6 @@ func (p personFieldDo) Where(conds ...gen.Condition) *personFieldDo { return p.withDO(p.DO.Where(conds...)) } -func (p personFieldDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *personFieldDo { - return p.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (p personFieldDo) Order(conds ...field.Expr) *personFieldDo { return p.withDO(p.DO.Order(conds...)) } diff --git a/dal/query/chii_person_relations.gen.go b/dal/query/chii_person_relations.gen.go new file mode 100644 index 000000000..2108efe27 --- /dev/null +++ b/dal/query/chii_person_relations.gen.go @@ -0,0 +1,367 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package query + +import ( + "context" + + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + + "gorm.io/gen" + "gorm.io/gen/field" + + "gorm.io/plugin/dbresolver" + + "github.com/bangumi/server/dal/dao" +) + +func newPersonRelation(db *gorm.DB, opts ...gen.DOOption) personRelation { + _personRelation := personRelation{} + + _personRelation.personRelationDo.UseDB(db, opts...) + _personRelation.personRelationDo.UseModel(&dao.PersonRelation{}) + + tableName := _personRelation.personRelationDo.TableName() + _personRelation.ALL = field.NewAsterisk(tableName) + _personRelation.ID = field.NewUint32(tableName, "rlt_id") + _personRelation.PersonType = field.NewString(tableName, "prsn_type") + _personRelation.PersonID = field.NewUint32(tableName, "prsn_id") + _personRelation.RelatedPersonType = field.NewString(tableName, "rlt_prsn_type") + _personRelation.RelatedPersonID = field.NewUint32(tableName, "rlt_prsn_id") + _personRelation.RelationType = field.NewUint32(tableName, "rlt_type") + _personRelation.Spoiler = field.NewBool(tableName, "rlt_spoiler") + _personRelation.Ended = field.NewBool(tableName, "rlt_ended") + _personRelation.ViceVersa = field.NewBool(tableName, "rlt_vice_versa") + _personRelation.Comment = field.NewString(tableName, "rlt_comment") + + _personRelation.fillFieldMap() + + return _personRelation +} + +type personRelation struct { + personRelationDo personRelationDo + + ALL field.Asterisk + ID field.Uint32 + PersonType field.String + PersonID field.Uint32 + RelatedPersonType field.String + RelatedPersonID field.Uint32 + RelationType field.Uint32 // 关联类型 + Spoiler field.Bool + Ended field.Bool + ViceVersa field.Bool + Comment field.String + + fieldMap map[string]field.Expr +} + +func (p personRelation) Table(newTableName string) *personRelation { + p.personRelationDo.UseTable(newTableName) + return p.updateTableName(newTableName) +} + +func (p personRelation) As(alias string) *personRelation { + p.personRelationDo.DO = *(p.personRelationDo.As(alias).(*gen.DO)) + return p.updateTableName(alias) +} + +func (p *personRelation) updateTableName(table string) *personRelation { + p.ALL = field.NewAsterisk(table) + p.ID = field.NewUint32(table, "rlt_id") + p.PersonType = field.NewString(table, "prsn_type") + p.PersonID = field.NewUint32(table, "prsn_id") + p.RelatedPersonType = field.NewString(table, "rlt_prsn_type") + p.RelatedPersonID = field.NewUint32(table, "rlt_prsn_id") + p.RelationType = field.NewUint32(table, "rlt_type") + p.Spoiler = field.NewBool(table, "rlt_spoiler") + p.Ended = field.NewBool(table, "rlt_ended") + p.ViceVersa = field.NewBool(table, "rlt_vice_versa") + p.Comment = field.NewString(table, "rlt_comment") + + p.fillFieldMap() + + return p +} + +func (p *personRelation) WithContext(ctx context.Context) *personRelationDo { + return p.personRelationDo.WithContext(ctx) +} + +func (p personRelation) TableName() string { return p.personRelationDo.TableName() } + +func (p personRelation) Alias() string { return p.personRelationDo.Alias() } + +func (p personRelation) Columns(cols ...field.Expr) gen.Columns { + return p.personRelationDo.Columns(cols...) +} + +func (p *personRelation) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := p.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (p *personRelation) fillFieldMap() { + p.fieldMap = make(map[string]field.Expr, 10) + p.fieldMap["rlt_id"] = p.ID + p.fieldMap["prsn_type"] = p.PersonType + p.fieldMap["prsn_id"] = p.PersonID + p.fieldMap["rlt_prsn_type"] = p.RelatedPersonType + p.fieldMap["rlt_prsn_id"] = p.RelatedPersonID + p.fieldMap["rlt_type"] = p.RelationType + p.fieldMap["rlt_spoiler"] = p.Spoiler + p.fieldMap["rlt_ended"] = p.Ended + p.fieldMap["rlt_vice_versa"] = p.ViceVersa + p.fieldMap["rlt_comment"] = p.Comment +} + +func (p personRelation) clone(db *gorm.DB) personRelation { + p.personRelationDo.ReplaceConnPool(db.Statement.ConnPool) + return p +} + +func (p personRelation) replaceDB(db *gorm.DB) personRelation { + p.personRelationDo.ReplaceDB(db) + return p +} + +type personRelationDo struct{ gen.DO } + +func (p personRelationDo) Debug() *personRelationDo { + return p.withDO(p.DO.Debug()) +} + +func (p personRelationDo) WithContext(ctx context.Context) *personRelationDo { + return p.withDO(p.DO.WithContext(ctx)) +} + +func (p personRelationDo) ReadDB() *personRelationDo { + return p.Clauses(dbresolver.Read) +} + +func (p personRelationDo) WriteDB() *personRelationDo { + return p.Clauses(dbresolver.Write) +} + +func (p personRelationDo) Session(config *gorm.Session) *personRelationDo { + return p.withDO(p.DO.Session(config)) +} + +func (p personRelationDo) Clauses(conds ...clause.Expression) *personRelationDo { + return p.withDO(p.DO.Clauses(conds...)) +} + +func (p personRelationDo) Returning(value interface{}, columns ...string) *personRelationDo { + return p.withDO(p.DO.Returning(value, columns...)) +} + +func (p personRelationDo) Not(conds ...gen.Condition) *personRelationDo { + return p.withDO(p.DO.Not(conds...)) +} + +func (p personRelationDo) Or(conds ...gen.Condition) *personRelationDo { + return p.withDO(p.DO.Or(conds...)) +} + +func (p personRelationDo) Select(conds ...field.Expr) *personRelationDo { + return p.withDO(p.DO.Select(conds...)) +} + +func (p personRelationDo) Where(conds ...gen.Condition) *personRelationDo { + return p.withDO(p.DO.Where(conds...)) +} + +func (p personRelationDo) Order(conds ...field.Expr) *personRelationDo { + return p.withDO(p.DO.Order(conds...)) +} + +func (p personRelationDo) Distinct(cols ...field.Expr) *personRelationDo { + return p.withDO(p.DO.Distinct(cols...)) +} + +func (p personRelationDo) Omit(cols ...field.Expr) *personRelationDo { + return p.withDO(p.DO.Omit(cols...)) +} + +func (p personRelationDo) Join(table schema.Tabler, on ...field.Expr) *personRelationDo { + return p.withDO(p.DO.Join(table, on...)) +} + +func (p personRelationDo) LeftJoin(table schema.Tabler, on ...field.Expr) *personRelationDo { + return p.withDO(p.DO.LeftJoin(table, on...)) +} + +func (p personRelationDo) RightJoin(table schema.Tabler, on ...field.Expr) *personRelationDo { + return p.withDO(p.DO.RightJoin(table, on...)) +} + +func (p personRelationDo) Group(cols ...field.Expr) *personRelationDo { + return p.withDO(p.DO.Group(cols...)) +} + +func (p personRelationDo) Having(conds ...gen.Condition) *personRelationDo { + return p.withDO(p.DO.Having(conds...)) +} + +func (p personRelationDo) Limit(limit int) *personRelationDo { + return p.withDO(p.DO.Limit(limit)) +} + +func (p personRelationDo) Offset(offset int) *personRelationDo { + return p.withDO(p.DO.Offset(offset)) +} + +func (p personRelationDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *personRelationDo { + return p.withDO(p.DO.Scopes(funcs...)) +} + +func (p personRelationDo) Unscoped() *personRelationDo { + return p.withDO(p.DO.Unscoped()) +} + +func (p personRelationDo) Create(values ...*dao.PersonRelation) error { + if len(values) == 0 { + return nil + } + return p.DO.Create(values) +} + +func (p personRelationDo) CreateInBatches(values []*dao.PersonRelation, batchSize int) error { + return p.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (p personRelationDo) Save(values ...*dao.PersonRelation) error { + if len(values) == 0 { + return nil + } + return p.DO.Save(values) +} + +func (p personRelationDo) First() (*dao.PersonRelation, error) { + if result, err := p.DO.First(); err != nil { + return nil, err + } else { + return result.(*dao.PersonRelation), nil + } +} + +func (p personRelationDo) Take() (*dao.PersonRelation, error) { + if result, err := p.DO.Take(); err != nil { + return nil, err + } else { + return result.(*dao.PersonRelation), nil + } +} + +func (p personRelationDo) Last() (*dao.PersonRelation, error) { + if result, err := p.DO.Last(); err != nil { + return nil, err + } else { + return result.(*dao.PersonRelation), nil + } +} + +func (p personRelationDo) Find() ([]*dao.PersonRelation, error) { + result, err := p.DO.Find() + return result.([]*dao.PersonRelation), err +} + +func (p personRelationDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*dao.PersonRelation, err error) { + buf := make([]*dao.PersonRelation, 0, batchSize) + err = p.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (p personRelationDo) FindInBatches(result *[]*dao.PersonRelation, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return p.DO.FindInBatches(result, batchSize, fc) +} + +func (p personRelationDo) Attrs(attrs ...field.AssignExpr) *personRelationDo { + return p.withDO(p.DO.Attrs(attrs...)) +} + +func (p personRelationDo) Assign(attrs ...field.AssignExpr) *personRelationDo { + return p.withDO(p.DO.Assign(attrs...)) +} + +func (p personRelationDo) Joins(fields ...field.RelationField) *personRelationDo { + for _, _f := range fields { + p = *p.withDO(p.DO.Joins(_f)) + } + return &p +} + +func (p personRelationDo) Preload(fields ...field.RelationField) *personRelationDo { + for _, _f := range fields { + p = *p.withDO(p.DO.Preload(_f)) + } + return &p +} + +func (p personRelationDo) FirstOrInit() (*dao.PersonRelation, error) { + if result, err := p.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*dao.PersonRelation), nil + } +} + +func (p personRelationDo) FirstOrCreate() (*dao.PersonRelation, error) { + if result, err := p.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*dao.PersonRelation), nil + } +} + +func (p personRelationDo) FindByPage(offset int, limit int) (result []*dao.PersonRelation, count int64, err error) { + result, err = p.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = p.Offset(-1).Limit(-1).Count() + return +} + +func (p personRelationDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = p.Count() + if err != nil { + return + } + + err = p.Offset(offset).Limit(limit).Scan(result) + return +} + +func (p personRelationDo) Scan(result interface{}) (err error) { + return p.DO.Scan(result) +} + +func (p personRelationDo) Delete(models ...*dao.PersonRelation) (result gen.ResultInfo, err error) { + return p.DO.Delete(models) +} + +func (p *personRelationDo) withDO(do gen.Dao) *personRelationDo { + p.DO = *do.(*gen.DO) + return p +} diff --git a/dal/query/chii_persons.gen.go b/dal/query/chii_persons.gen.go index 083c175b6..919ae68d2 100644 --- a/dal/query/chii_persons.gen.go +++ b/dal/query/chii_persons.gen.go @@ -28,9 +28,9 @@ func newPerson(db *gorm.DB, opts ...gen.DOOption) person { tableName := _person.personDo.TableName() _person.ALL = field.NewAsterisk(tableName) _person.ID = field.NewUint32(tableName, "prsn_id") - _person.Name = field.NewString(tableName, "prsn_name") + _person.Name = field.NewField(tableName, "prsn_name") _person.Type = field.NewUint8(tableName, "prsn_type") - _person.Infobox = field.NewString(tableName, "prsn_infobox") + _person.Infobox = field.NewField(tableName, "prsn_infobox") _person.Producer = field.NewBool(tableName, "prsn_producer") _person.Mangaka = field.NewBool(tableName, "prsn_mangaka") _person.Artist = field.NewBool(tableName, "prsn_artist") @@ -61,14 +61,15 @@ func newPerson(db *gorm.DB, opts ...gen.DOOption) person { return _person } +// person (现实)人物表 type person struct { personDo personDo ALL field.Asterisk ID field.Uint32 - Name field.String + Name field.Field Type field.Uint8 // 个人,公司,组合 - Infobox field.String + Infobox field.Field Producer field.Bool Mangaka field.Bool Artist field.Bool @@ -106,9 +107,9 @@ func (p person) As(alias string) *person { func (p *person) updateTableName(table string) *person { p.ALL = field.NewAsterisk(table) p.ID = field.NewUint32(table, "prsn_id") - p.Name = field.NewString(table, "prsn_name") + p.Name = field.NewField(table, "prsn_name") p.Type = field.NewUint8(table, "prsn_type") - p.Infobox = field.NewString(table, "prsn_infobox") + p.Infobox = field.NewField(table, "prsn_infobox") p.Producer = field.NewBool(table, "prsn_producer") p.Mangaka = field.NewBool(table, "prsn_mangaka") p.Artist = field.NewBool(table, "prsn_artist") @@ -140,6 +141,8 @@ func (p person) TableName() string { return p.personDo.TableName() } func (p person) Alias() string { return p.personDo.Alias() } +func (p person) Columns(cols ...field.Expr) gen.Columns { return p.personDo.Columns(cols...) } + func (p *person) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := p.fieldMap[fieldName] if !ok || _f == nil { @@ -179,11 +182,14 @@ func (p *person) fillFieldMap() { func (p person) clone(db *gorm.DB) person { p.personDo.ReplaceConnPool(db.Statement.ConnPool) + p.Fields.db = db.Session(&gorm.Session{Initialized: true}) + p.Fields.db.Statement.ConnPool = db.Statement.ConnPool return p } func (p person) replaceDB(db *gorm.DB) person { p.personDo.ReplaceDB(db) + p.Fields.db = db.Session(&gorm.Session{}) return p } @@ -211,10 +217,20 @@ func (a personHasOneFields) WithContext(ctx context.Context) *personHasOneFields return &a } +func (a personHasOneFields) Session(session *gorm.Session) *personHasOneFields { + a.db = a.db.Session(session) + return &a +} + func (a personHasOneFields) Model(m *dao.Person) *personHasOneFieldsTx { return &personHasOneFieldsTx{a.db.Model(m).Association(a.Name())} } +func (a personHasOneFields) Unscoped() *personHasOneFields { + a.db = a.db.Unscoped() + return &a +} + type personHasOneFieldsTx struct{ tx *gorm.Association } func (a personHasOneFieldsTx) Find() (result *dao.PersonField, err error) { @@ -253,6 +269,11 @@ func (a personHasOneFieldsTx) Count() int64 { return a.tx.Count() } +func (a personHasOneFieldsTx) Unscoped() *personHasOneFieldsTx { + a.tx = a.tx.Unscoped() + return &a +} + type personDo struct{ gen.DO } func (p personDo) Debug() *personDo { @@ -299,10 +320,6 @@ func (p personDo) Where(conds ...gen.Condition) *personDo { return p.withDO(p.DO.Where(conds...)) } -func (p personDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *personDo { - return p.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (p personDo) Order(conds ...field.Expr) *personDo { return p.withDO(p.DO.Order(conds...)) } diff --git a/dal/query/chii_pms.gen.go b/dal/query/chii_pms.gen.go index cda94647d..c8e0851a8 100644 --- a/dal/query/chii_pms.gen.go +++ b/dal/query/chii_pms.gen.go @@ -103,6 +103,10 @@ func (p privateMessage) TableName() string { return p.privateMessageDo.TableName func (p privateMessage) Alias() string { return p.privateMessageDo.Alias() } +func (p privateMessage) Columns(cols ...field.Expr) gen.Columns { + return p.privateMessageDo.Columns(cols...) +} + func (p *privateMessage) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := p.fieldMap[fieldName] if !ok || _f == nil { @@ -184,10 +188,6 @@ func (p privateMessageDo) Where(conds ...gen.Condition) *privateMessageDo { return p.withDO(p.DO.Where(conds...)) } -func (p privateMessageDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *privateMessageDo { - return p.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (p privateMessageDo) Order(conds ...field.Expr) *privateMessageDo { return p.withDO(p.DO.Order(conds...)) } diff --git a/dal/query/chii_rev_history.gen.go b/dal/query/chii_rev_history.gen.go index e5b1dc31d..6c5cd22ee 100644 --- a/dal/query/chii_rev_history.gen.go +++ b/dal/query/chii_rev_history.gen.go @@ -88,6 +88,10 @@ func (r revisionHistory) TableName() string { return r.revisionHistoryDo.TableNa func (r revisionHistory) Alias() string { return r.revisionHistoryDo.Alias() } +func (r revisionHistory) Columns(cols ...field.Expr) gen.Columns { + return r.revisionHistoryDo.Columns(cols...) +} + func (r *revisionHistory) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := r.fieldMap[fieldName] if !ok || _f == nil { @@ -164,10 +168,6 @@ func (r revisionHistoryDo) Where(conds ...gen.Condition) *revisionHistoryDo { return r.withDO(r.DO.Where(conds...)) } -func (r revisionHistoryDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *revisionHistoryDo { - return r.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (r revisionHistoryDo) Order(conds ...field.Expr) *revisionHistoryDo { return r.withDO(r.DO.Order(conds...)) } diff --git a/dal/query/chii_rev_text.gen.go b/dal/query/chii_rev_text.gen.go index f2c47888b..2cb3dab7d 100644 --- a/dal/query/chii_rev_text.gen.go +++ b/dal/query/chii_rev_text.gen.go @@ -73,6 +73,10 @@ func (r revisionText) TableName() string { return r.revisionTextDo.TableName() } func (r revisionText) Alias() string { return r.revisionTextDo.Alias() } +func (r revisionText) Columns(cols ...field.Expr) gen.Columns { + return r.revisionTextDo.Columns(cols...) +} + func (r *revisionText) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := r.fieldMap[fieldName] if !ok || _f == nil { @@ -144,10 +148,6 @@ func (r revisionTextDo) Where(conds ...gen.Condition) *revisionTextDo { return r.withDO(r.DO.Where(conds...)) } -func (r revisionTextDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *revisionTextDo { - return r.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (r revisionTextDo) Order(conds ...field.Expr) *revisionTextDo { return r.withDO(r.DO.Order(conds...)) } diff --git a/dal/query/chii_subject_fields.gen.go b/dal/query/chii_subject_fields.gen.go index f92ae5eb0..194938696 100644 --- a/dal/query/chii_subject_fields.gen.go +++ b/dal/query/chii_subject_fields.gen.go @@ -127,6 +127,10 @@ func (s subjectField) TableName() string { return s.subjectFieldDo.TableName() } func (s subjectField) Alias() string { return s.subjectFieldDo.Alias() } +func (s subjectField) Columns(cols ...field.Expr) gen.Columns { + return s.subjectFieldDo.Columns(cols...) +} + func (s *subjectField) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := s.fieldMap[fieldName] if !ok || _f == nil { @@ -216,10 +220,6 @@ func (s subjectFieldDo) Where(conds ...gen.Condition) *subjectFieldDo { return s.withDO(s.DO.Where(conds...)) } -func (s subjectFieldDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *subjectFieldDo { - return s.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (s subjectFieldDo) Order(conds ...field.Expr) *subjectFieldDo { return s.withDO(s.DO.Order(conds...)) } diff --git a/dal/query/chii_subject_interests.gen.go b/dal/query/chii_subject_interests.gen.go index 3fe5de838..33f7b38ff 100644 --- a/dal/query/chii_subject_interests.gen.go +++ b/dal/query/chii_subject_interests.gen.go @@ -127,6 +127,10 @@ func (s subjectCollection) TableName() string { return s.subjectCollectionDo.Tab func (s subjectCollection) Alias() string { return s.subjectCollectionDo.Alias() } +func (s subjectCollection) Columns(cols ...field.Expr) gen.Columns { + return s.subjectCollectionDo.Columns(cols...) +} + func (s *subjectCollection) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := s.fieldMap[fieldName] if !ok || _f == nil { @@ -216,10 +220,6 @@ func (s subjectCollectionDo) Where(conds ...gen.Condition) *subjectCollectionDo return s.withDO(s.DO.Where(conds...)) } -func (s subjectCollectionDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *subjectCollectionDo { - return s.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (s subjectCollectionDo) Order(conds ...field.Expr) *subjectCollectionDo { return s.withDO(s.DO.Order(conds...)) } diff --git a/dal/query/chii_subject_relations.gen.go b/dal/query/chii_subject_relations.gen.go index 3668403dd..caa59094a 100644 --- a/dal/query/chii_subject_relations.gen.go +++ b/dal/query/chii_subject_relations.gen.go @@ -33,7 +33,7 @@ func newSubjectRelation(db *gorm.DB, opts ...gen.DOOption) subjectRelation { _subjectRelation.RelatedSubjectID = field.NewUint32(tableName, "rlt_related_subject_id") _subjectRelation.RelatedSubjectTypeID = field.NewUint8(tableName, "rlt_related_subject_type_id") _subjectRelation.ViceVersa = field.NewBool(tableName, "rlt_vice_versa") - _subjectRelation.Order = field.NewUint8(tableName, "rlt_order") + _subjectRelation.Order = field.NewUint16(tableName, "rlt_order") _subjectRelation.Subject = subjectRelationHasOneSubject{ db: db.Session(&gorm.Session{}), @@ -50,6 +50,7 @@ func newSubjectRelation(db *gorm.DB, opts ...gen.DOOption) subjectRelation { return _subjectRelation } +// subjectRelation 条目关联表 type subjectRelation struct { subjectRelationDo subjectRelationDo @@ -60,7 +61,7 @@ type subjectRelation struct { RelatedSubjectID field.Uint32 // 关联目标 ID RelatedSubjectTypeID field.Uint8 // 关联目标类型 ViceVersa field.Bool - Order field.Uint8 // 关联排序 + Order field.Uint16 // 关联排序 Subject subjectRelationHasOneSubject fieldMap map[string]field.Expr @@ -84,7 +85,7 @@ func (s *subjectRelation) updateTableName(table string) *subjectRelation { s.RelatedSubjectID = field.NewUint32(table, "rlt_related_subject_id") s.RelatedSubjectTypeID = field.NewUint8(table, "rlt_related_subject_type_id") s.ViceVersa = field.NewBool(table, "rlt_vice_versa") - s.Order = field.NewUint8(table, "rlt_order") + s.Order = field.NewUint16(table, "rlt_order") s.fillFieldMap() @@ -99,6 +100,10 @@ func (s subjectRelation) TableName() string { return s.subjectRelationDo.TableNa func (s subjectRelation) Alias() string { return s.subjectRelationDo.Alias() } +func (s subjectRelation) Columns(cols ...field.Expr) gen.Columns { + return s.subjectRelationDo.Columns(cols...) +} + func (s *subjectRelation) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := s.fieldMap[fieldName] if !ok || _f == nil { @@ -122,11 +127,14 @@ func (s *subjectRelation) fillFieldMap() { func (s subjectRelation) clone(db *gorm.DB) subjectRelation { s.subjectRelationDo.ReplaceConnPool(db.Statement.ConnPool) + s.Subject.db = db.Session(&gorm.Session{Initialized: true}) + s.Subject.db.Statement.ConnPool = db.Statement.ConnPool return s } func (s subjectRelation) replaceDB(db *gorm.DB) subjectRelation { s.subjectRelationDo.ReplaceDB(db) + s.Subject.db = db.Session(&gorm.Session{}) return s } @@ -158,10 +166,20 @@ func (a subjectRelationHasOneSubject) WithContext(ctx context.Context) *subjectR return &a } +func (a subjectRelationHasOneSubject) Session(session *gorm.Session) *subjectRelationHasOneSubject { + a.db = a.db.Session(session) + return &a +} + func (a subjectRelationHasOneSubject) Model(m *dao.SubjectRelation) *subjectRelationHasOneSubjectTx { return &subjectRelationHasOneSubjectTx{a.db.Model(m).Association(a.Name())} } +func (a subjectRelationHasOneSubject) Unscoped() *subjectRelationHasOneSubject { + a.db = a.db.Unscoped() + return &a +} + type subjectRelationHasOneSubjectTx struct{ tx *gorm.Association } func (a subjectRelationHasOneSubjectTx) Find() (result *dao.Subject, err error) { @@ -200,6 +218,11 @@ func (a subjectRelationHasOneSubjectTx) Count() int64 { return a.tx.Count() } +func (a subjectRelationHasOneSubjectTx) Unscoped() *subjectRelationHasOneSubjectTx { + a.tx = a.tx.Unscoped() + return &a +} + type subjectRelationDo struct{ gen.DO } func (s subjectRelationDo) Debug() *subjectRelationDo { @@ -246,10 +269,6 @@ func (s subjectRelationDo) Where(conds ...gen.Condition) *subjectRelationDo { return s.withDO(s.DO.Where(conds...)) } -func (s subjectRelationDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *subjectRelationDo { - return s.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (s subjectRelationDo) Order(conds ...field.Expr) *subjectRelationDo { return s.withDO(s.DO.Order(conds...)) } diff --git a/dal/query/chii_subject_revisions.gen.go b/dal/query/chii_subject_revisions.gen.go index 9b07a96bb..f27a25761 100644 --- a/dal/query/chii_subject_revisions.gen.go +++ b/dal/query/chii_subject_revisions.gen.go @@ -36,6 +36,7 @@ func newSubjectRevision(db *gorm.DB, opts ...gen.DOOption) subjectRevision { _subjectRevision.Name = field.NewString(tableName, "rev_name") _subjectRevision.NameCN = field.NewString(tableName, "rev_name_cn") _subjectRevision.FieldInfobox = field.NewString(tableName, "rev_field_infobox") + _subjectRevision.FieldMetaTags = field.NewString(tableName, "rev_field_meta_tags") _subjectRevision.FieldSummary = field.NewString(tableName, "rev_field_summary") _subjectRevision.VoteField = field.NewString(tableName, "rev_vote_field") _subjectRevision.FieldEps = field.NewUint32(tableName, "rev_field_eps") @@ -60,22 +61,23 @@ func newSubjectRevision(db *gorm.DB, opts ...gen.DOOption) subjectRevision { type subjectRevision struct { subjectRevisionDo subjectRevisionDo - ALL field.Asterisk - ID field.Uint32 - Type field.Uint8 // 修订类型 - SubjectID field.Uint32 - TypeID field.Uint16 - CreatorID field.Uint32 - Dateline field.Uint32 - Name field.String - NameCN field.String - FieldInfobox field.String - FieldSummary field.String - VoteField field.String - FieldEps field.Uint32 - EditSummary field.String - Platform field.Uint16 - Subject subjectRevisionBelongsToSubject + ALL field.Asterisk + ID field.Uint32 + Type field.Uint8 // 修订类型 + SubjectID field.Uint32 + TypeID field.Uint16 + CreatorID field.Uint32 + Dateline field.Uint32 + Name field.String + NameCN field.String + FieldInfobox field.String + FieldMetaTags field.String + FieldSummary field.String + VoteField field.String + FieldEps field.Uint32 + EditSummary field.String + Platform field.Uint16 + Subject subjectRevisionBelongsToSubject fieldMap map[string]field.Expr } @@ -101,6 +103,7 @@ func (s *subjectRevision) updateTableName(table string) *subjectRevision { s.Name = field.NewString(table, "rev_name") s.NameCN = field.NewString(table, "rev_name_cn") s.FieldInfobox = field.NewString(table, "rev_field_infobox") + s.FieldMetaTags = field.NewString(table, "rev_field_meta_tags") s.FieldSummary = field.NewString(table, "rev_field_summary") s.VoteField = field.NewString(table, "rev_vote_field") s.FieldEps = field.NewUint32(table, "rev_field_eps") @@ -120,6 +123,10 @@ func (s subjectRevision) TableName() string { return s.subjectRevisionDo.TableNa func (s subjectRevision) Alias() string { return s.subjectRevisionDo.Alias() } +func (s subjectRevision) Columns(cols ...field.Expr) gen.Columns { + return s.subjectRevisionDo.Columns(cols...) +} + func (s *subjectRevision) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := s.fieldMap[fieldName] if !ok || _f == nil { @@ -130,7 +137,7 @@ func (s *subjectRevision) GetFieldByName(fieldName string) (field.OrderExpr, boo } func (s *subjectRevision) fillFieldMap() { - s.fieldMap = make(map[string]field.Expr, 15) + s.fieldMap = make(map[string]field.Expr, 16) s.fieldMap["rev_id"] = s.ID s.fieldMap["rev_type"] = s.Type s.fieldMap["rev_subject_id"] = s.SubjectID @@ -140,6 +147,7 @@ func (s *subjectRevision) fillFieldMap() { s.fieldMap["rev_name"] = s.Name s.fieldMap["rev_name_cn"] = s.NameCN s.fieldMap["rev_field_infobox"] = s.FieldInfobox + s.fieldMap["rev_field_meta_tags"] = s.FieldMetaTags s.fieldMap["rev_field_summary"] = s.FieldSummary s.fieldMap["rev_vote_field"] = s.VoteField s.fieldMap["rev_field_eps"] = s.FieldEps @@ -150,11 +158,14 @@ func (s *subjectRevision) fillFieldMap() { func (s subjectRevision) clone(db *gorm.DB) subjectRevision { s.subjectRevisionDo.ReplaceConnPool(db.Statement.ConnPool) + s.Subject.db = db.Session(&gorm.Session{Initialized: true}) + s.Subject.db.Statement.ConnPool = db.Statement.ConnPool return s } func (s subjectRevision) replaceDB(db *gorm.DB) subjectRevision { s.subjectRevisionDo.ReplaceDB(db) + s.Subject.db = db.Session(&gorm.Session{}) return s } @@ -186,10 +197,20 @@ func (a subjectRevisionBelongsToSubject) WithContext(ctx context.Context) *subje return &a } +func (a subjectRevisionBelongsToSubject) Session(session *gorm.Session) *subjectRevisionBelongsToSubject { + a.db = a.db.Session(session) + return &a +} + func (a subjectRevisionBelongsToSubject) Model(m *dao.SubjectRevision) *subjectRevisionBelongsToSubjectTx { return &subjectRevisionBelongsToSubjectTx{a.db.Model(m).Association(a.Name())} } +func (a subjectRevisionBelongsToSubject) Unscoped() *subjectRevisionBelongsToSubject { + a.db = a.db.Unscoped() + return &a +} + type subjectRevisionBelongsToSubjectTx struct{ tx *gorm.Association } func (a subjectRevisionBelongsToSubjectTx) Find() (result *dao.Subject, err error) { @@ -228,6 +249,11 @@ func (a subjectRevisionBelongsToSubjectTx) Count() int64 { return a.tx.Count() } +func (a subjectRevisionBelongsToSubjectTx) Unscoped() *subjectRevisionBelongsToSubjectTx { + a.tx = a.tx.Unscoped() + return &a +} + type subjectRevisionDo struct{ gen.DO } func (s subjectRevisionDo) Debug() *subjectRevisionDo { @@ -274,10 +300,6 @@ func (s subjectRevisionDo) Where(conds ...gen.Condition) *subjectRevisionDo { return s.withDO(s.DO.Where(conds...)) } -func (s subjectRevisionDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *subjectRevisionDo { - return s.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (s subjectRevisionDo) Order(conds ...field.Expr) *subjectRevisionDo { return s.withDO(s.DO.Order(conds...)) } diff --git a/dal/query/chii_subjects.gen.go b/dal/query/chii_subjects.gen.go index 4076cbb44..d91ab6079 100644 --- a/dal/query/chii_subjects.gen.go +++ b/dal/query/chii_subjects.gen.go @@ -29,14 +29,15 @@ func newSubject(db *gorm.DB, opts ...gen.DOOption) subject { _subject.ALL = field.NewAsterisk(tableName) _subject.ID = field.NewUint32(tableName, "subject_id") _subject.TypeID = field.NewUint8(tableName, "subject_type_id") - _subject.Name = field.NewString(tableName, "subject_name") - _subject.NameCN = field.NewString(tableName, "subject_name_cn") + _subject.Name = field.NewField(tableName, "subject_name") + _subject.NameCN = field.NewField(tableName, "subject_name_cn") _subject.UID = field.NewString(tableName, "subject_uid") _subject.Creator = field.NewUint32(tableName, "subject_creator") _subject.Dateline = field.NewUint32(tableName, "subject_dateline") _subject.Image = field.NewString(tableName, "subject_image") _subject.Platform = field.NewUint16(tableName, "subject_platform") - _subject.Infobox = field.NewString(tableName, "field_infobox") + _subject.Infobox = field.NewField(tableName, "field_infobox") + _subject.FieldMetaTags = field.NewString(tableName, "field_meta_tags") _subject.Summary = field.NewString(tableName, "field_summary") _subject.Field5 = field.NewString(tableName, "field_5") _subject.Volumes = field.NewUint32(tableName, "field_volumes") @@ -66,33 +67,34 @@ func newSubject(db *gorm.DB, opts ...gen.DOOption) subject { type subject struct { subjectDo subjectDo - ALL field.Asterisk - ID field.Uint32 - TypeID field.Uint8 - Name field.String - NameCN field.String - UID field.String // isbn / imdb - Creator field.Uint32 - Dateline field.Uint32 - Image field.String - Platform field.Uint16 - Infobox field.String - Summary field.String // summary - Field5 field.String // author summary - Volumes field.Uint32 // 卷数 - Eps field.Uint32 - Wish field.Uint32 - Done field.Uint32 - Doing field.Uint32 - OnHold field.Uint32 // 搁置人数 - Dropped field.Uint32 // 抛弃人数 - Series field.Bool - SeriesEntry field.Uint32 - IdxCn field.String - Airtime field.Uint8 - Nsfw field.Bool - Ban field.Uint8 - Fields subjectHasOneFields + ALL field.Asterisk + ID field.Uint32 + TypeID field.Uint8 + Name field.Field + NameCN field.Field + UID field.String // isbn / imdb + Creator field.Uint32 + Dateline field.Uint32 + Image field.String + Platform field.Uint16 + Infobox field.Field + FieldMetaTags field.String + Summary field.String // summary + Field5 field.String // author summary + Volumes field.Uint32 // 卷数 + Eps field.Uint32 + Wish field.Uint32 + Done field.Uint32 + Doing field.Uint32 + OnHold field.Uint32 // 搁置人数 + Dropped field.Uint32 // 抛弃人数 + Series field.Bool + SeriesEntry field.Uint32 + IdxCn field.String + Airtime field.Uint8 + Nsfw field.Bool + Ban field.Uint8 + Fields subjectHasOneFields fieldMap map[string]field.Expr } @@ -111,14 +113,15 @@ func (s *subject) updateTableName(table string) *subject { s.ALL = field.NewAsterisk(table) s.ID = field.NewUint32(table, "subject_id") s.TypeID = field.NewUint8(table, "subject_type_id") - s.Name = field.NewString(table, "subject_name") - s.NameCN = field.NewString(table, "subject_name_cn") + s.Name = field.NewField(table, "subject_name") + s.NameCN = field.NewField(table, "subject_name_cn") s.UID = field.NewString(table, "subject_uid") s.Creator = field.NewUint32(table, "subject_creator") s.Dateline = field.NewUint32(table, "subject_dateline") s.Image = field.NewString(table, "subject_image") s.Platform = field.NewUint16(table, "subject_platform") - s.Infobox = field.NewString(table, "field_infobox") + s.Infobox = field.NewField(table, "field_infobox") + s.FieldMetaTags = field.NewString(table, "field_meta_tags") s.Summary = field.NewString(table, "field_summary") s.Field5 = field.NewString(table, "field_5") s.Volumes = field.NewUint32(table, "field_volumes") @@ -146,6 +149,8 @@ func (s subject) TableName() string { return s.subjectDo.TableName() } func (s subject) Alias() string { return s.subjectDo.Alias() } +func (s subject) Columns(cols ...field.Expr) gen.Columns { return s.subjectDo.Columns(cols...) } + func (s *subject) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := s.fieldMap[fieldName] if !ok || _f == nil { @@ -156,7 +161,7 @@ func (s *subject) GetFieldByName(fieldName string) (field.OrderExpr, bool) { } func (s *subject) fillFieldMap() { - s.fieldMap = make(map[string]field.Expr, 26) + s.fieldMap = make(map[string]field.Expr, 27) s.fieldMap["subject_id"] = s.ID s.fieldMap["subject_type_id"] = s.TypeID s.fieldMap["subject_name"] = s.Name @@ -167,6 +172,7 @@ func (s *subject) fillFieldMap() { s.fieldMap["subject_image"] = s.Image s.fieldMap["subject_platform"] = s.Platform s.fieldMap["field_infobox"] = s.Infobox + s.fieldMap["field_meta_tags"] = s.FieldMetaTags s.fieldMap["field_summary"] = s.Summary s.fieldMap["field_5"] = s.Field5 s.fieldMap["field_volumes"] = s.Volumes @@ -187,11 +193,14 @@ func (s *subject) fillFieldMap() { func (s subject) clone(db *gorm.DB) subject { s.subjectDo.ReplaceConnPool(db.Statement.ConnPool) + s.Fields.db = db.Session(&gorm.Session{Initialized: true}) + s.Fields.db.Statement.ConnPool = db.Statement.ConnPool return s } func (s subject) replaceDB(db *gorm.DB) subject { s.subjectDo.ReplaceDB(db) + s.Fields.db = db.Session(&gorm.Session{}) return s } @@ -219,10 +228,20 @@ func (a subjectHasOneFields) WithContext(ctx context.Context) *subjectHasOneFiel return &a } +func (a subjectHasOneFields) Session(session *gorm.Session) *subjectHasOneFields { + a.db = a.db.Session(session) + return &a +} + func (a subjectHasOneFields) Model(m *dao.Subject) *subjectHasOneFieldsTx { return &subjectHasOneFieldsTx{a.db.Model(m).Association(a.Name())} } +func (a subjectHasOneFields) Unscoped() *subjectHasOneFields { + a.db = a.db.Unscoped() + return &a +} + type subjectHasOneFieldsTx struct{ tx *gorm.Association } func (a subjectHasOneFieldsTx) Find() (result *dao.SubjectField, err error) { @@ -261,6 +280,11 @@ func (a subjectHasOneFieldsTx) Count() int64 { return a.tx.Count() } +func (a subjectHasOneFieldsTx) Unscoped() *subjectHasOneFieldsTx { + a.tx = a.tx.Unscoped() + return &a +} + type subjectDo struct{ gen.DO } func (s subjectDo) Debug() *subjectDo { @@ -307,10 +331,6 @@ func (s subjectDo) Where(conds ...gen.Condition) *subjectDo { return s.withDO(s.DO.Where(conds...)) } -func (s subjectDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *subjectDo { - return s.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (s subjectDo) Order(conds ...field.Expr) *subjectDo { return s.withDO(s.DO.Order(conds...)) } diff --git a/dal/query/chii_tag_neue_index.gen.go b/dal/query/chii_tag_neue_index.gen.go new file mode 100644 index 000000000..630c6d284 --- /dev/null +++ b/dal/query/chii_tag_neue_index.gen.go @@ -0,0 +1,351 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package query + +import ( + "context" + + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + + "gorm.io/gen" + "gorm.io/gen/field" + + "gorm.io/plugin/dbresolver" + + "github.com/bangumi/server/dal/dao" +) + +func newTagIndex(db *gorm.DB, opts ...gen.DOOption) tagIndex { + _tagIndex := tagIndex{} + + _tagIndex.tagIndexDo.UseDB(db, opts...) + _tagIndex.tagIndexDo.UseModel(&dao.TagIndex{}) + + tableName := _tagIndex.tagIndexDo.TableName() + _tagIndex.ALL = field.NewAsterisk(tableName) + _tagIndex.ID = field.NewUint32(tableName, "tag_id") + _tagIndex.Name = field.NewString(tableName, "tag_name") + _tagIndex.Cat = field.NewInt8(tableName, "tag_cat") + _tagIndex.Type = field.NewUint8(tableName, "tag_type") + _tagIndex.Results = field.NewUint32(tableName, "tag_results") + _tagIndex.CreatedTime = field.NewUint32(tableName, "tag_dateline") + _tagIndex.UpdatedTime = field.NewUint32(tableName, "tag_lasttouch") + + _tagIndex.fillFieldMap() + + return _tagIndex +} + +type tagIndex struct { + tagIndexDo tagIndexDo + + ALL field.Asterisk + ID field.Uint32 + Name field.String + Cat field.Int8 // 0=条目 1=日志 2=天窗 + Type field.Uint8 + Results field.Uint32 + CreatedTime field.Uint32 + UpdatedTime field.Uint32 + + fieldMap map[string]field.Expr +} + +func (t tagIndex) Table(newTableName string) *tagIndex { + t.tagIndexDo.UseTable(newTableName) + return t.updateTableName(newTableName) +} + +func (t tagIndex) As(alias string) *tagIndex { + t.tagIndexDo.DO = *(t.tagIndexDo.As(alias).(*gen.DO)) + return t.updateTableName(alias) +} + +func (t *tagIndex) updateTableName(table string) *tagIndex { + t.ALL = field.NewAsterisk(table) + t.ID = field.NewUint32(table, "tag_id") + t.Name = field.NewString(table, "tag_name") + t.Cat = field.NewInt8(table, "tag_cat") + t.Type = field.NewUint8(table, "tag_type") + t.Results = field.NewUint32(table, "tag_results") + t.CreatedTime = field.NewUint32(table, "tag_dateline") + t.UpdatedTime = field.NewUint32(table, "tag_lasttouch") + + t.fillFieldMap() + + return t +} + +func (t *tagIndex) WithContext(ctx context.Context) *tagIndexDo { return t.tagIndexDo.WithContext(ctx) } + +func (t tagIndex) TableName() string { return t.tagIndexDo.TableName() } + +func (t tagIndex) Alias() string { return t.tagIndexDo.Alias() } + +func (t tagIndex) Columns(cols ...field.Expr) gen.Columns { return t.tagIndexDo.Columns(cols...) } + +func (t *tagIndex) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := t.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (t *tagIndex) fillFieldMap() { + t.fieldMap = make(map[string]field.Expr, 7) + t.fieldMap["tag_id"] = t.ID + t.fieldMap["tag_name"] = t.Name + t.fieldMap["tag_cat"] = t.Cat + t.fieldMap["tag_type"] = t.Type + t.fieldMap["tag_results"] = t.Results + t.fieldMap["tag_dateline"] = t.CreatedTime + t.fieldMap["tag_lasttouch"] = t.UpdatedTime +} + +func (t tagIndex) clone(db *gorm.DB) tagIndex { + t.tagIndexDo.ReplaceConnPool(db.Statement.ConnPool) + return t +} + +func (t tagIndex) replaceDB(db *gorm.DB) tagIndex { + t.tagIndexDo.ReplaceDB(db) + return t +} + +type tagIndexDo struct{ gen.DO } + +func (t tagIndexDo) Debug() *tagIndexDo { + return t.withDO(t.DO.Debug()) +} + +func (t tagIndexDo) WithContext(ctx context.Context) *tagIndexDo { + return t.withDO(t.DO.WithContext(ctx)) +} + +func (t tagIndexDo) ReadDB() *tagIndexDo { + return t.Clauses(dbresolver.Read) +} + +func (t tagIndexDo) WriteDB() *tagIndexDo { + return t.Clauses(dbresolver.Write) +} + +func (t tagIndexDo) Session(config *gorm.Session) *tagIndexDo { + return t.withDO(t.DO.Session(config)) +} + +func (t tagIndexDo) Clauses(conds ...clause.Expression) *tagIndexDo { + return t.withDO(t.DO.Clauses(conds...)) +} + +func (t tagIndexDo) Returning(value interface{}, columns ...string) *tagIndexDo { + return t.withDO(t.DO.Returning(value, columns...)) +} + +func (t tagIndexDo) Not(conds ...gen.Condition) *tagIndexDo { + return t.withDO(t.DO.Not(conds...)) +} + +func (t tagIndexDo) Or(conds ...gen.Condition) *tagIndexDo { + return t.withDO(t.DO.Or(conds...)) +} + +func (t tagIndexDo) Select(conds ...field.Expr) *tagIndexDo { + return t.withDO(t.DO.Select(conds...)) +} + +func (t tagIndexDo) Where(conds ...gen.Condition) *tagIndexDo { + return t.withDO(t.DO.Where(conds...)) +} + +func (t tagIndexDo) Order(conds ...field.Expr) *tagIndexDo { + return t.withDO(t.DO.Order(conds...)) +} + +func (t tagIndexDo) Distinct(cols ...field.Expr) *tagIndexDo { + return t.withDO(t.DO.Distinct(cols...)) +} + +func (t tagIndexDo) Omit(cols ...field.Expr) *tagIndexDo { + return t.withDO(t.DO.Omit(cols...)) +} + +func (t tagIndexDo) Join(table schema.Tabler, on ...field.Expr) *tagIndexDo { + return t.withDO(t.DO.Join(table, on...)) +} + +func (t tagIndexDo) LeftJoin(table schema.Tabler, on ...field.Expr) *tagIndexDo { + return t.withDO(t.DO.LeftJoin(table, on...)) +} + +func (t tagIndexDo) RightJoin(table schema.Tabler, on ...field.Expr) *tagIndexDo { + return t.withDO(t.DO.RightJoin(table, on...)) +} + +func (t tagIndexDo) Group(cols ...field.Expr) *tagIndexDo { + return t.withDO(t.DO.Group(cols...)) +} + +func (t tagIndexDo) Having(conds ...gen.Condition) *tagIndexDo { + return t.withDO(t.DO.Having(conds...)) +} + +func (t tagIndexDo) Limit(limit int) *tagIndexDo { + return t.withDO(t.DO.Limit(limit)) +} + +func (t tagIndexDo) Offset(offset int) *tagIndexDo { + return t.withDO(t.DO.Offset(offset)) +} + +func (t tagIndexDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *tagIndexDo { + return t.withDO(t.DO.Scopes(funcs...)) +} + +func (t tagIndexDo) Unscoped() *tagIndexDo { + return t.withDO(t.DO.Unscoped()) +} + +func (t tagIndexDo) Create(values ...*dao.TagIndex) error { + if len(values) == 0 { + return nil + } + return t.DO.Create(values) +} + +func (t tagIndexDo) CreateInBatches(values []*dao.TagIndex, batchSize int) error { + return t.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (t tagIndexDo) Save(values ...*dao.TagIndex) error { + if len(values) == 0 { + return nil + } + return t.DO.Save(values) +} + +func (t tagIndexDo) First() (*dao.TagIndex, error) { + if result, err := t.DO.First(); err != nil { + return nil, err + } else { + return result.(*dao.TagIndex), nil + } +} + +func (t tagIndexDo) Take() (*dao.TagIndex, error) { + if result, err := t.DO.Take(); err != nil { + return nil, err + } else { + return result.(*dao.TagIndex), nil + } +} + +func (t tagIndexDo) Last() (*dao.TagIndex, error) { + if result, err := t.DO.Last(); err != nil { + return nil, err + } else { + return result.(*dao.TagIndex), nil + } +} + +func (t tagIndexDo) Find() ([]*dao.TagIndex, error) { + result, err := t.DO.Find() + return result.([]*dao.TagIndex), err +} + +func (t tagIndexDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*dao.TagIndex, err error) { + buf := make([]*dao.TagIndex, 0, batchSize) + err = t.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (t tagIndexDo) FindInBatches(result *[]*dao.TagIndex, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return t.DO.FindInBatches(result, batchSize, fc) +} + +func (t tagIndexDo) Attrs(attrs ...field.AssignExpr) *tagIndexDo { + return t.withDO(t.DO.Attrs(attrs...)) +} + +func (t tagIndexDo) Assign(attrs ...field.AssignExpr) *tagIndexDo { + return t.withDO(t.DO.Assign(attrs...)) +} + +func (t tagIndexDo) Joins(fields ...field.RelationField) *tagIndexDo { + for _, _f := range fields { + t = *t.withDO(t.DO.Joins(_f)) + } + return &t +} + +func (t tagIndexDo) Preload(fields ...field.RelationField) *tagIndexDo { + for _, _f := range fields { + t = *t.withDO(t.DO.Preload(_f)) + } + return &t +} + +func (t tagIndexDo) FirstOrInit() (*dao.TagIndex, error) { + if result, err := t.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*dao.TagIndex), nil + } +} + +func (t tagIndexDo) FirstOrCreate() (*dao.TagIndex, error) { + if result, err := t.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*dao.TagIndex), nil + } +} + +func (t tagIndexDo) FindByPage(offset int, limit int) (result []*dao.TagIndex, count int64, err error) { + result, err = t.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = t.Offset(-1).Limit(-1).Count() + return +} + +func (t tagIndexDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = t.Count() + if err != nil { + return + } + + err = t.Offset(offset).Limit(limit).Scan(result) + return +} + +func (t tagIndexDo) Scan(result interface{}) (err error) { + return t.DO.Scan(result) +} + +func (t tagIndexDo) Delete(models ...*dao.TagIndex) (result gen.ResultInfo, err error) { + return t.DO.Delete(models) +} + +func (t *tagIndexDo) withDO(do gen.Dao) *tagIndexDo { + t.DO = *do.(*gen.DO) + return t +} diff --git a/dal/query/chii_tag_neue_list.gen.go b/dal/query/chii_tag_neue_list.gen.go new file mode 100644 index 000000000..f21d036af --- /dev/null +++ b/dal/query/chii_tag_neue_list.gen.go @@ -0,0 +1,438 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package query + +import ( + "context" + + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + + "gorm.io/gen" + "gorm.io/gen/field" + + "gorm.io/plugin/dbresolver" + + "github.com/bangumi/server/dal/dao" +) + +func newTagList(db *gorm.DB, opts ...gen.DOOption) tagList { + _tagList := tagList{} + + _tagList.tagListDo.UseDB(db, opts...) + _tagList.tagListDo.UseModel(&dao.TagList{}) + + tableName := _tagList.tagListDo.TableName() + _tagList.ALL = field.NewAsterisk(tableName) + _tagList.Tid = field.NewUint32(tableName, "tlt_tid") + _tagList.UID = field.NewUint32(tableName, "tlt_uid") + _tagList.Cat = field.NewUint8(tableName, "tlt_cat") + _tagList.Type = field.NewUint8(tableName, "tlt_type") + _tagList.Mid = field.NewUint32(tableName, "tlt_mid") + _tagList.CreatedTime = field.NewUint32(tableName, "tlt_dateline") + _tagList.Tag = tagListHasOneTag{ + db: db.Session(&gorm.Session{}), + + RelationField: field.NewRelation("Tag", "dao.TagIndex"), + } + + _tagList.fillFieldMap() + + return _tagList +} + +type tagList struct { + tagListDo tagListDo + + ALL field.Asterisk + Tid field.Uint32 + UID field.Uint32 + Cat field.Uint8 + Type field.Uint8 + Mid field.Uint32 + CreatedTime field.Uint32 + Tag tagListHasOneTag + + fieldMap map[string]field.Expr +} + +func (t tagList) Table(newTableName string) *tagList { + t.tagListDo.UseTable(newTableName) + return t.updateTableName(newTableName) +} + +func (t tagList) As(alias string) *tagList { + t.tagListDo.DO = *(t.tagListDo.As(alias).(*gen.DO)) + return t.updateTableName(alias) +} + +func (t *tagList) updateTableName(table string) *tagList { + t.ALL = field.NewAsterisk(table) + t.Tid = field.NewUint32(table, "tlt_tid") + t.UID = field.NewUint32(table, "tlt_uid") + t.Cat = field.NewUint8(table, "tlt_cat") + t.Type = field.NewUint8(table, "tlt_type") + t.Mid = field.NewUint32(table, "tlt_mid") + t.CreatedTime = field.NewUint32(table, "tlt_dateline") + + t.fillFieldMap() + + return t +} + +func (t *tagList) WithContext(ctx context.Context) *tagListDo { return t.tagListDo.WithContext(ctx) } + +func (t tagList) TableName() string { return t.tagListDo.TableName() } + +func (t tagList) Alias() string { return t.tagListDo.Alias() } + +func (t tagList) Columns(cols ...field.Expr) gen.Columns { return t.tagListDo.Columns(cols...) } + +func (t *tagList) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := t.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (t *tagList) fillFieldMap() { + t.fieldMap = make(map[string]field.Expr, 7) + t.fieldMap["tlt_tid"] = t.Tid + t.fieldMap["tlt_uid"] = t.UID + t.fieldMap["tlt_cat"] = t.Cat + t.fieldMap["tlt_type"] = t.Type + t.fieldMap["tlt_mid"] = t.Mid + t.fieldMap["tlt_dateline"] = t.CreatedTime + +} + +func (t tagList) clone(db *gorm.DB) tagList { + t.tagListDo.ReplaceConnPool(db.Statement.ConnPool) + t.Tag.db = db.Session(&gorm.Session{Initialized: true}) + t.Tag.db.Statement.ConnPool = db.Statement.ConnPool + return t +} + +func (t tagList) replaceDB(db *gorm.DB) tagList { + t.tagListDo.ReplaceDB(db) + t.Tag.db = db.Session(&gorm.Session{}) + return t +} + +type tagListHasOneTag struct { + db *gorm.DB + + field.RelationField +} + +func (a tagListHasOneTag) Where(conds ...field.Expr) *tagListHasOneTag { + if len(conds) == 0 { + return &a + } + + exprs := make([]clause.Expression, 0, len(conds)) + for _, cond := range conds { + exprs = append(exprs, cond.BeCond().(clause.Expression)) + } + a.db = a.db.Clauses(clause.Where{Exprs: exprs}) + return &a +} + +func (a tagListHasOneTag) WithContext(ctx context.Context) *tagListHasOneTag { + a.db = a.db.WithContext(ctx) + return &a +} + +func (a tagListHasOneTag) Session(session *gorm.Session) *tagListHasOneTag { + a.db = a.db.Session(session) + return &a +} + +func (a tagListHasOneTag) Model(m *dao.TagList) *tagListHasOneTagTx { + return &tagListHasOneTagTx{a.db.Model(m).Association(a.Name())} +} + +func (a tagListHasOneTag) Unscoped() *tagListHasOneTag { + a.db = a.db.Unscoped() + return &a +} + +type tagListHasOneTagTx struct{ tx *gorm.Association } + +func (a tagListHasOneTagTx) Find() (result *dao.TagIndex, err error) { + return result, a.tx.Find(&result) +} + +func (a tagListHasOneTagTx) Append(values ...*dao.TagIndex) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Append(targetValues...) +} + +func (a tagListHasOneTagTx) Replace(values ...*dao.TagIndex) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Replace(targetValues...) +} + +func (a tagListHasOneTagTx) Delete(values ...*dao.TagIndex) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Delete(targetValues...) +} + +func (a tagListHasOneTagTx) Clear() error { + return a.tx.Clear() +} + +func (a tagListHasOneTagTx) Count() int64 { + return a.tx.Count() +} + +func (a tagListHasOneTagTx) Unscoped() *tagListHasOneTagTx { + a.tx = a.tx.Unscoped() + return &a +} + +type tagListDo struct{ gen.DO } + +func (t tagListDo) Debug() *tagListDo { + return t.withDO(t.DO.Debug()) +} + +func (t tagListDo) WithContext(ctx context.Context) *tagListDo { + return t.withDO(t.DO.WithContext(ctx)) +} + +func (t tagListDo) ReadDB() *tagListDo { + return t.Clauses(dbresolver.Read) +} + +func (t tagListDo) WriteDB() *tagListDo { + return t.Clauses(dbresolver.Write) +} + +func (t tagListDo) Session(config *gorm.Session) *tagListDo { + return t.withDO(t.DO.Session(config)) +} + +func (t tagListDo) Clauses(conds ...clause.Expression) *tagListDo { + return t.withDO(t.DO.Clauses(conds...)) +} + +func (t tagListDo) Returning(value interface{}, columns ...string) *tagListDo { + return t.withDO(t.DO.Returning(value, columns...)) +} + +func (t tagListDo) Not(conds ...gen.Condition) *tagListDo { + return t.withDO(t.DO.Not(conds...)) +} + +func (t tagListDo) Or(conds ...gen.Condition) *tagListDo { + return t.withDO(t.DO.Or(conds...)) +} + +func (t tagListDo) Select(conds ...field.Expr) *tagListDo { + return t.withDO(t.DO.Select(conds...)) +} + +func (t tagListDo) Where(conds ...gen.Condition) *tagListDo { + return t.withDO(t.DO.Where(conds...)) +} + +func (t tagListDo) Order(conds ...field.Expr) *tagListDo { + return t.withDO(t.DO.Order(conds...)) +} + +func (t tagListDo) Distinct(cols ...field.Expr) *tagListDo { + return t.withDO(t.DO.Distinct(cols...)) +} + +func (t tagListDo) Omit(cols ...field.Expr) *tagListDo { + return t.withDO(t.DO.Omit(cols...)) +} + +func (t tagListDo) Join(table schema.Tabler, on ...field.Expr) *tagListDo { + return t.withDO(t.DO.Join(table, on...)) +} + +func (t tagListDo) LeftJoin(table schema.Tabler, on ...field.Expr) *tagListDo { + return t.withDO(t.DO.LeftJoin(table, on...)) +} + +func (t tagListDo) RightJoin(table schema.Tabler, on ...field.Expr) *tagListDo { + return t.withDO(t.DO.RightJoin(table, on...)) +} + +func (t tagListDo) Group(cols ...field.Expr) *tagListDo { + return t.withDO(t.DO.Group(cols...)) +} + +func (t tagListDo) Having(conds ...gen.Condition) *tagListDo { + return t.withDO(t.DO.Having(conds...)) +} + +func (t tagListDo) Limit(limit int) *tagListDo { + return t.withDO(t.DO.Limit(limit)) +} + +func (t tagListDo) Offset(offset int) *tagListDo { + return t.withDO(t.DO.Offset(offset)) +} + +func (t tagListDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *tagListDo { + return t.withDO(t.DO.Scopes(funcs...)) +} + +func (t tagListDo) Unscoped() *tagListDo { + return t.withDO(t.DO.Unscoped()) +} + +func (t tagListDo) Create(values ...*dao.TagList) error { + if len(values) == 0 { + return nil + } + return t.DO.Create(values) +} + +func (t tagListDo) CreateInBatches(values []*dao.TagList, batchSize int) error { + return t.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (t tagListDo) Save(values ...*dao.TagList) error { + if len(values) == 0 { + return nil + } + return t.DO.Save(values) +} + +func (t tagListDo) First() (*dao.TagList, error) { + if result, err := t.DO.First(); err != nil { + return nil, err + } else { + return result.(*dao.TagList), nil + } +} + +func (t tagListDo) Take() (*dao.TagList, error) { + if result, err := t.DO.Take(); err != nil { + return nil, err + } else { + return result.(*dao.TagList), nil + } +} + +func (t tagListDo) Last() (*dao.TagList, error) { + if result, err := t.DO.Last(); err != nil { + return nil, err + } else { + return result.(*dao.TagList), nil + } +} + +func (t tagListDo) Find() ([]*dao.TagList, error) { + result, err := t.DO.Find() + return result.([]*dao.TagList), err +} + +func (t tagListDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*dao.TagList, err error) { + buf := make([]*dao.TagList, 0, batchSize) + err = t.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (t tagListDo) FindInBatches(result *[]*dao.TagList, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return t.DO.FindInBatches(result, batchSize, fc) +} + +func (t tagListDo) Attrs(attrs ...field.AssignExpr) *tagListDo { + return t.withDO(t.DO.Attrs(attrs...)) +} + +func (t tagListDo) Assign(attrs ...field.AssignExpr) *tagListDo { + return t.withDO(t.DO.Assign(attrs...)) +} + +func (t tagListDo) Joins(fields ...field.RelationField) *tagListDo { + for _, _f := range fields { + t = *t.withDO(t.DO.Joins(_f)) + } + return &t +} + +func (t tagListDo) Preload(fields ...field.RelationField) *tagListDo { + for _, _f := range fields { + t = *t.withDO(t.DO.Preload(_f)) + } + return &t +} + +func (t tagListDo) FirstOrInit() (*dao.TagList, error) { + if result, err := t.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*dao.TagList), nil + } +} + +func (t tagListDo) FirstOrCreate() (*dao.TagList, error) { + if result, err := t.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*dao.TagList), nil + } +} + +func (t tagListDo) FindByPage(offset int, limit int) (result []*dao.TagList, count int64, err error) { + result, err = t.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = t.Offset(-1).Limit(-1).Count() + return +} + +func (t tagListDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = t.Count() + if err != nil { + return + } + + err = t.Offset(offset).Limit(limit).Scan(result) + return +} + +func (t tagListDo) Scan(result interface{}) (err error) { + return t.DO.Scan(result) +} + +func (t tagListDo) Delete(models ...*dao.TagList) (result gen.ResultInfo, err error) { + return t.DO.Delete(models) +} + +func (t *tagListDo) withDO(do gen.Dao) *tagListDo { + t.DO = *do.(*gen.DO) + return t +} diff --git a/dal/query/chii_usergroup.gen.go b/dal/query/chii_usergroup.gen.go index 295e294b1..89e06272c 100644 --- a/dal/query/chii_usergroup.gen.go +++ b/dal/query/chii_usergroup.gen.go @@ -79,6 +79,8 @@ func (u userGroup) TableName() string { return u.userGroupDo.TableName() } func (u userGroup) Alias() string { return u.userGroupDo.Alias() } +func (u userGroup) Columns(cols ...field.Expr) gen.Columns { return u.userGroupDo.Columns(cols...) } + func (u *userGroup) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := u.fieldMap[fieldName] if !ok || _f == nil { @@ -152,10 +154,6 @@ func (u userGroupDo) Where(conds ...gen.Condition) *userGroupDo { return u.withDO(u.DO.Where(conds...)) } -func (u userGroupDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *userGroupDo { - return u.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) -} - func (u userGroupDo) Order(conds ...field.Expr) *userGroupDo { return u.withDO(u.DO.Order(conds...)) } diff --git a/internal/pkg/generic/conv.go b/dal/query/export_db.go similarity index 88% rename from internal/pkg/generic/conv.go rename to dal/query/export_db.go index e162f05b7..bac903af6 100644 --- a/internal/pkg/generic/conv.go +++ b/dal/query/export_db.go @@ -12,11 +12,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see -package generic +package query -func BtoI(b bool) int { - if b { - return 1 - } - return 0 +import ( + "gorm.io/gorm" +) + +func (q *Query) DB() *gorm.DB { + return q.db } diff --git a/dal/query/gen.go b/dal/query/gen.go index eb46eb9af..191d4805a 100644 --- a/dal/query/gen.go +++ b/dal/query/gen.go @@ -32,7 +32,9 @@ func Use(db *gorm.DB, opts ...gen.DOOption) *Query { Notification: newNotification(db, opts...), NotificationField: newNotificationField(db, opts...), Person: newPerson(db, opts...), + PersonCollect: newPersonCollect(db, opts...), PersonField: newPersonField(db, opts...), + PersonRelation: newPersonRelation(db, opts...), PersonSubjects: newPersonSubjects(db, opts...), PrivateMessage: newPrivateMessage(db, opts...), RevisionHistory: newRevisionHistory(db, opts...), @@ -42,6 +44,8 @@ func Use(db *gorm.DB, opts ...gen.DOOption) *Query { SubjectField: newSubjectField(db, opts...), SubjectRelation: newSubjectRelation(db, opts...), SubjectRevision: newSubjectRevision(db, opts...), + TagIndex: newTagIndex(db, opts...), + TagList: newTagList(db, opts...), UserGroup: newUserGroup(db, opts...), WebSession: newWebSession(db, opts...), } @@ -64,7 +68,9 @@ type Query struct { Notification notification NotificationField notificationField Person person + PersonCollect personCollect PersonField personField + PersonRelation personRelation PersonSubjects personSubjects PrivateMessage privateMessage RevisionHistory revisionHistory @@ -74,6 +80,8 @@ type Query struct { SubjectField subjectField SubjectRelation subjectRelation SubjectRevision subjectRevision + TagIndex tagIndex + TagList tagList UserGroup userGroup WebSession webSession } @@ -97,7 +105,9 @@ func (q *Query) clone(db *gorm.DB) *Query { Notification: q.Notification.clone(db), NotificationField: q.NotificationField.clone(db), Person: q.Person.clone(db), + PersonCollect: q.PersonCollect.clone(db), PersonField: q.PersonField.clone(db), + PersonRelation: q.PersonRelation.clone(db), PersonSubjects: q.PersonSubjects.clone(db), PrivateMessage: q.PrivateMessage.clone(db), RevisionHistory: q.RevisionHistory.clone(db), @@ -107,6 +117,8 @@ func (q *Query) clone(db *gorm.DB) *Query { SubjectField: q.SubjectField.clone(db), SubjectRelation: q.SubjectRelation.clone(db), SubjectRevision: q.SubjectRevision.clone(db), + TagIndex: q.TagIndex.clone(db), + TagList: q.TagList.clone(db), UserGroup: q.UserGroup.clone(db), WebSession: q.WebSession.clone(db), } @@ -137,7 +149,9 @@ func (q *Query) ReplaceDB(db *gorm.DB) *Query { Notification: q.Notification.replaceDB(db), NotificationField: q.NotificationField.replaceDB(db), Person: q.Person.replaceDB(db), + PersonCollect: q.PersonCollect.replaceDB(db), PersonField: q.PersonField.replaceDB(db), + PersonRelation: q.PersonRelation.replaceDB(db), PersonSubjects: q.PersonSubjects.replaceDB(db), PrivateMessage: q.PrivateMessage.replaceDB(db), RevisionHistory: q.RevisionHistory.replaceDB(db), @@ -147,6 +161,8 @@ func (q *Query) ReplaceDB(db *gorm.DB) *Query { SubjectField: q.SubjectField.replaceDB(db), SubjectRelation: q.SubjectRelation.replaceDB(db), SubjectRevision: q.SubjectRevision.replaceDB(db), + TagIndex: q.TagIndex.replaceDB(db), + TagList: q.TagList.replaceDB(db), UserGroup: q.UserGroup.replaceDB(db), WebSession: q.WebSession.replaceDB(db), } @@ -167,7 +183,9 @@ type queryCtx struct { Notification *notificationDo NotificationField *notificationFieldDo Person *personDo + PersonCollect *personCollectDo PersonField *personFieldDo + PersonRelation *personRelationDo PersonSubjects *personSubjectsDo PrivateMessage *privateMessageDo RevisionHistory *revisionHistoryDo @@ -177,6 +195,8 @@ type queryCtx struct { SubjectField *subjectFieldDo SubjectRelation *subjectRelationDo SubjectRevision *subjectRevisionDo + TagIndex *tagIndexDo + TagList *tagListDo UserGroup *userGroupDo WebSession *webSessionDo } @@ -197,7 +217,9 @@ func (q *Query) WithContext(ctx context.Context) *queryCtx { Notification: q.Notification.WithContext(ctx), NotificationField: q.NotificationField.WithContext(ctx), Person: q.Person.WithContext(ctx), + PersonCollect: q.PersonCollect.WithContext(ctx), PersonField: q.PersonField.WithContext(ctx), + PersonRelation: q.PersonRelation.WithContext(ctx), PersonSubjects: q.PersonSubjects.WithContext(ctx), PrivateMessage: q.PrivateMessage.WithContext(ctx), RevisionHistory: q.RevisionHistory.WithContext(ctx), @@ -207,6 +229,8 @@ func (q *Query) WithContext(ctx context.Context) *queryCtx { SubjectField: q.SubjectField.WithContext(ctx), SubjectRelation: q.SubjectRelation.WithContext(ctx), SubjectRevision: q.SubjectRevision.WithContext(ctx), + TagIndex: q.TagIndex.WithContext(ctx), + TagList: q.TagList.WithContext(ctx), UserGroup: q.UserGroup.WithContext(ctx), WebSession: q.WebSession.WithContext(ctx), } @@ -217,10 +241,14 @@ func (q *Query) Transaction(fc func(tx *Query) error, opts ...*sql.TxOptions) er } func (q *Query) Begin(opts ...*sql.TxOptions) *QueryTx { - return &QueryTx{q.clone(q.db.Begin(opts...))} + tx := q.db.Begin(opts...) + return &QueryTx{Query: q.clone(tx), Error: tx.Error} } -type QueryTx struct{ *Query } +type QueryTx struct { + *Query + Error error +} func (q *QueryTx) Commit() error { return q.db.Commit().Error diff --git a/dal/utiltype/string.go b/dal/utiltype/string.go index 6166072fa..0bad6495e 100644 --- a/dal/utiltype/string.go +++ b/dal/utiltype/string.go @@ -42,7 +42,6 @@ func (s *HTMLEscapedString) Scan(src any) error { return nil } - //nolint:goerr113 return fmt.Errorf("utiltype.HTMLEscapedString: unsupported input type %s", reflect.TypeOf(src).String()) } diff --git a/domain/gerr/error.go b/domain/gerr/error.go index a53ece344..a0c49a6e7 100644 --- a/domain/gerr/error.go +++ b/domain/gerr/error.go @@ -36,6 +36,8 @@ var ErrInvisibleChar = errors.New("input contains invisible chars") var ErrExists = errors.New("item already exists") +var ErrBanned = errors.New("you are not allowed to do this") + var ErrInvalidData = errors.New("invalid data") func WrapGormError(err error) error { diff --git a/domain/relation.go b/domain/relation.go index 6240c906f..ac9b6714c 100644 --- a/domain/relation.go +++ b/domain/relation.go @@ -29,6 +29,7 @@ type SubjectPersonRelation struct { PersonID model.PersonID SubjectID model.SubjectID + Eps string } type SubjectCharacterRelation struct { diff --git a/etc/Dockerfile b/etc/Dockerfile index 9af79d840..cce2f539d 100644 --- a/etc/Dockerfile +++ b/etc/Dockerfile @@ -1,4 +1,4 @@ -FROM gcr.io/distroless/static +FROM gcr.io/distroless/static@sha256:47b2d72ff90843eb8a768b5c2f89b40741843b639d065b9b937b07cd59b479c6 ENTRYPOINT ["/app/chii.exe"] diff --git a/etc/mock.task.yaml b/etc/mock.task.yaml deleted file mode 100644 index cf7c598de..000000000 --- a/etc/mock.task.yaml +++ /dev/null @@ -1,275 +0,0 @@ -version: "3" - -tasks: - all: - - task: gen - - gen: - cmds: - - task: session-manager - - task: session-repo - - task: cache - - task: Auth - - task: CharacterRepo - - task: IndexRepo - - task: RevisionRepo - - task: SubjectRepo - - task: UserRepo - - task: EpisodeRepo - - task: PersonRepo - - task: PersonService - - task: CollectionRepo - - task: TimeLineService - - task: SearchClient - - task: PrivateMessageRepo - - task: NotificationRepo - - base-mock: - cmds: - - cmd: mockery --dir {{.SRC_DIR}} --filename '{{.MOCK_STRUCT|default .INTERFACE}}.go' --name '{{.INTERFACE | default "Manager"}}' --structname {{.MOCK_STRUCT|default .INTERFACE}} --output ./internal/mocks --with-expecter - - session-repo: - sources: - - ./web/session/repo.go - - ./internal/pkg/tools/go.mod - generates: - - internal/mocks/SessionRepo.go - cmds: - - task: base-mock - vars: - SRC_DIR: ./web/session - INTERFACE: Repo - MOCK_STRUCT: "SessionRepo" - - cache: - sources: - - ./internal/pkg/cache/redis.go - - ./internal/pkg/tools/go.mod - generates: - - internal/mocks/RedisCache.go - cmds: - - task: base-mock - vars: - SRC_DIR: ./internal/pkg/cache - INTERFACE: "RedisCache" - MOCK_STRUCT: RedisCache - - session-manager: - sources: - - ./web/session/manager.go - - ./internal/pkg/tools/go.mod - generates: - - internal/mocks/SessionManager.go - cmds: - - task: base-mock - vars: - MOCK_STRUCT: SessionManager - SRC_DIR: ./web/session - - "Auth": - sources: - - internal/auth/domain.go - - ./internal/pkg/tools/go.mod - generates: - - internal/mocks/AuthRepo.go - - internal/mocks/AuthService.go - cmds: - - task: base-mock - vars: - SRC_DIR: ./internal/auth - INTERFACE: "Repo" - MOCK_STRUCT: "AuthRepo" - - task: base-mock - vars: - SRC_DIR: ./internal/auth - MOCK_STRUCT: "AuthService" - INTERFACE: Service - - "CharacterRepo": - sources: - - ./internal/character/domain/.go - - ./internal/pkg/tools/go.mod - generates: - - internal/mocks/CharacterRepo.go - cmds: - - task: base-mock - vars: - SRC_DIR: ./internal/character - MOCK_STRUCT: "CharacterRepo" - INTERFACE: Repo - - "IndexRepo": - sources: - - internal/index/domain.go - - ./internal/pkg/tools/go.mod - generates: - - internal/mocks/IndexRepo.go - cmds: - - task: base-mock - vars: - SRC_DIR: ./internal/index - MOCK_STRUCT: "IndexRepo" - INTERFACE: Repo - - "RevisionRepo": - sources: - - internal/revision/domain.go - - ./internal/pkg/tools/go.mod - generates: - - internal/mocks/RevisionRepo.go - cmds: - - task: base-mock - vars: - SRC_DIR: ./internal/revision - MOCK_STRUCT: "RevisionRepo" - INTERFACE: Repo - - "SubjectRepo": - sources: - - internal/subject/domain.go - - ./internal/pkg/tools/go.mod - generates: - - internal/mocks/SubjectRepo.go - - internal/mocks/SubjectCachedRepo.go - cmds: - - task: base-mock - vars: - SRC_DIR: ./internal/subject - INTERFACE: "Repo" - MOCK_STRUCT: SubjectRepo - - - task: base-mock - vars: - SRC_DIR: ./internal/subject - INTERFACE: "CachedRepo" - MOCK_STRUCT: SubjectCachedRepo - - "UserRepo": - sources: - - internal/user/domain.go - - ./internal/pkg/tools/go.mod - generates: - - internal/mocks/UserRepo.go - cmds: - - task: base-mock - vars: - SRC_DIR: ./internal/user - MOCK_STRUCT: "UserRepo" - INTERFACE: Repo - - "EpisodeRepo": - sources: - - internal/episode/domain.go - - ./internal/pkg/tools/go.mod - generates: - - internal/mocks/EpisodeRepo.go - cmds: - - task: base-mock - vars: - SRC_DIR: ./internal/episode - MOCK_STRUCT: "EpisodeRepo" - INTERFACE: Repo - - "PersonRepo": - sources: - - internal/person/domain.go - - ./internal/pkg/tools/go.mod - generates: - - internal/mocks/PersonRepo.go - cmds: - - task: base-mock - vars: - SRC_DIR: ./internal/person - MOCK_STRUCT: "PersonRepo" - INTERFACE: Repo - - "PersonService": - sources: - - internal/person/domain.go - - ./internal/pkg/tools/go.mod - generates: - - internal/mocks/PersonService.go - cmds: - - task: base-mock - vars: - SRC_DIR: ./internal/person - MOCK_STRUCT: "PersonService" - INTERFACE: Repo - - GroupRepo: - sources: - - internal/group/domain.go - - ./internal/pkg/tools/go.mod - generates: - - internal/mocks/GroupRepo.go - cmds: - - task: base-mock - vars: - SRC_DIR: ./internal/group - MOCK_STRUCT: GroupRepo - INTERFACE: Repo - - CollectionRepo: - sources: - - internal/collections/domain.go - - ./internal/pkg/tools/go.mod - generates: - - internal/mocks/CollectionRepo.go - cmds: - - task: base-mock - vars: - SRC_DIR: ./internal/collections - MOCK_STRUCT: CollectionRepo - INTERFACE: Repo - - TimeLineService: - sources: - - internal/timeline/domain.go - - ./internal/pkg/tools/go.mod - generates: - - internal/mocks/TimeLineService.go - cmds: - - task: base-mock - vars: - SRC_DIR: ./internal/timeline - INTERFACE: Service - MOCK_STRUCT: TimeLineService - - SearchClient: - sources: - - internal/search/client.go - - ./internal/pkg/tools/go.mod - generates: - - internal/mocks/SearchClient.go - cmds: - - task: base-mock - vars: - SRC_DIR: ./internal/search - INTERFACE: Client - MOCK_STRUCT: SearchClient - - PrivateMessageRepo: - sources: - - internal/pm/domain.go - - ./internal/pkg/tools/go.mod - generates: - - internal/mocks/PrivateMessageRepo.go - cmds: - - task: base-mock - vars: - SRC_DIR: ./internal/pm - INTERFACE: Repo - MOCK_STRUCT: PrivateMessageRepo - - NotificationRepo: - sources: - - internal/notification/domain.go - - ./internal/pkg/tools/go.mod - generates: - - internal/mocks/NotificationRepo.go - cmds: - - task: base-mock - vars: - SRC_DIR: ./internal/notification - INTERFACE: Repo - MOCK_STRUCT: NotificationRepo diff --git a/generated/proto/go/api/v1/timeline.pb.go b/generated/proto/go/api/v1/timeline.pb.go deleted file mode 100644 index b46b91bb6..000000000 --- a/generated/proto/go/api/v1/timeline.pb.go +++ /dev/null @@ -1,939 +0,0 @@ -// grpc server to create timeline - -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.30.0 -// protoc (unknown) -// source: api/v1/timeline.proto - -package api - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type HelloRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` -} - -func (x *HelloRequest) Reset() { - *x = HelloRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_v1_timeline_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *HelloRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*HelloRequest) ProtoMessage() {} - -func (x *HelloRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_timeline_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead. -func (*HelloRequest) Descriptor() ([]byte, []int) { - return file_api_v1_timeline_proto_rawDescGZIP(), []int{0} -} - -func (x *HelloRequest) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -type HelloResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` -} - -func (x *HelloResponse) Reset() { - *x = HelloResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_v1_timeline_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *HelloResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*HelloResponse) ProtoMessage() {} - -func (x *HelloResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_timeline_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use HelloResponse.ProtoReflect.Descriptor instead. -func (*HelloResponse) Descriptor() ([]byte, []int) { - return file_api_v1_timeline_proto_rawDescGZIP(), []int{1} -} - -func (x *HelloResponse) GetMessage() string { - if x != nil { - return x.Message - } - return "" -} - -type SubjectCollectResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Ok bool `protobuf:"varint,1,opt,name=ok,proto3" json:"ok,omitempty"` -} - -func (x *SubjectCollectResponse) Reset() { - *x = SubjectCollectResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_v1_timeline_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SubjectCollectResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SubjectCollectResponse) ProtoMessage() {} - -func (x *SubjectCollectResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_timeline_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SubjectCollectResponse.ProtoReflect.Descriptor instead. -func (*SubjectCollectResponse) Descriptor() ([]byte, []int) { - return file_api_v1_timeline_proto_rawDescGZIP(), []int{2} -} - -func (x *SubjectCollectResponse) GetOk() bool { - if x != nil { - return x.Ok - } - return false -} - -type SubjectProgressResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Ok bool `protobuf:"varint,1,opt,name=ok,proto3" json:"ok,omitempty"` -} - -func (x *SubjectProgressResponse) Reset() { - *x = SubjectProgressResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_v1_timeline_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SubjectProgressResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SubjectProgressResponse) ProtoMessage() {} - -func (x *SubjectProgressResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_timeline_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SubjectProgressResponse.ProtoReflect.Descriptor instead. -func (*SubjectProgressResponse) Descriptor() ([]byte, []int) { - return file_api_v1_timeline_proto_rawDescGZIP(), []int{3} -} - -func (x *SubjectProgressResponse) GetOk() bool { - if x != nil { - return x.Ok - } - return false -} - -type EpisodeCollectResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Ok bool `protobuf:"varint,1,opt,name=ok,proto3" json:"ok,omitempty"` -} - -func (x *EpisodeCollectResponse) Reset() { - *x = EpisodeCollectResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_v1_timeline_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *EpisodeCollectResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*EpisodeCollectResponse) ProtoMessage() {} - -func (x *EpisodeCollectResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_timeline_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use EpisodeCollectResponse.ProtoReflect.Descriptor instead. -func (*EpisodeCollectResponse) Descriptor() ([]byte, []int) { - return file_api_v1_timeline_proto_rawDescGZIP(), []int{4} -} - -func (x *EpisodeCollectResponse) GetOk() bool { - if x != nil { - return x.Ok - } - return false -} - -type Subject struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` - Type uint32 `protobuf:"varint,2,opt,name=type,proto3" json:"type,omitempty"` - Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` - NameCn string `protobuf:"bytes,4,opt,name=name_cn,json=nameCn,proto3" json:"name_cn,omitempty"` - Image string `protobuf:"bytes,5,opt,name=image,proto3" json:"image,omitempty"` - Series bool `protobuf:"varint,6,opt,name=series,proto3" json:"series,omitempty"` - VolsTotal uint32 `protobuf:"varint,7,opt,name=vols_total,json=volsTotal,proto3" json:"vols_total,omitempty"` - EpsTotal uint32 `protobuf:"varint,8,opt,name=eps_total,json=epsTotal,proto3" json:"eps_total,omitempty"` -} - -func (x *Subject) Reset() { - *x = Subject{} - if protoimpl.UnsafeEnabled { - mi := &file_api_v1_timeline_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Subject) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Subject) ProtoMessage() {} - -func (x *Subject) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_timeline_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Subject.ProtoReflect.Descriptor instead. -func (*Subject) Descriptor() ([]byte, []int) { - return file_api_v1_timeline_proto_rawDescGZIP(), []int{5} -} - -func (x *Subject) GetId() uint32 { - if x != nil { - return x.Id - } - return 0 -} - -func (x *Subject) GetType() uint32 { - if x != nil { - return x.Type - } - return 0 -} - -func (x *Subject) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *Subject) GetNameCn() string { - if x != nil { - return x.NameCn - } - return "" -} - -func (x *Subject) GetImage() string { - if x != nil { - return x.Image - } - return "" -} - -func (x *Subject) GetSeries() bool { - if x != nil { - return x.Series - } - return false -} - -func (x *Subject) GetVolsTotal() uint32 { - if x != nil { - return x.VolsTotal - } - return 0 -} - -func (x *Subject) GetEpsTotal() uint32 { - if x != nil { - return x.EpsTotal - } - return 0 -} - -type Episode struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` - Type uint32 `protobuf:"varint,2,opt,name=type,proto3" json:"type,omitempty"` - Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` - NameCn string `protobuf:"bytes,4,opt,name=name_cn,json=nameCn,proto3" json:"name_cn,omitempty"` - Sort float64 `protobuf:"fixed64,5,opt,name=sort,proto3" json:"sort,omitempty"` -} - -func (x *Episode) Reset() { - *x = Episode{} - if protoimpl.UnsafeEnabled { - mi := &file_api_v1_timeline_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Episode) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Episode) ProtoMessage() {} - -func (x *Episode) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_timeline_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Episode.ProtoReflect.Descriptor instead. -func (*Episode) Descriptor() ([]byte, []int) { - return file_api_v1_timeline_proto_rawDescGZIP(), []int{6} -} - -func (x *Episode) GetId() uint32 { - if x != nil { - return x.Id - } - return 0 -} - -func (x *Episode) GetType() uint32 { - if x != nil { - return x.Type - } - return 0 -} - -func (x *Episode) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *Episode) GetNameCn() string { - if x != nil { - return x.NameCn - } - return "" -} - -func (x *Episode) GetSort() float64 { - if x != nil { - return x.Sort - } - return 0 -} - -// The request message containing the user's name. -type SubjectCollectRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - UserId uint64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - Subject *Subject `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` - Collection uint32 `protobuf:"varint,3,opt,name=collection,proto3" json:"collection,omitempty"` - Comment string `protobuf:"bytes,4,opt,name=comment,proto3" json:"comment,omitempty"` - Rate uint32 `protobuf:"varint,5,opt,name=rate,proto3" json:"rate,omitempty"` -} - -func (x *SubjectCollectRequest) Reset() { - *x = SubjectCollectRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_v1_timeline_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SubjectCollectRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SubjectCollectRequest) ProtoMessage() {} - -func (x *SubjectCollectRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_timeline_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SubjectCollectRequest.ProtoReflect.Descriptor instead. -func (*SubjectCollectRequest) Descriptor() ([]byte, []int) { - return file_api_v1_timeline_proto_rawDescGZIP(), []int{7} -} - -func (x *SubjectCollectRequest) GetUserId() uint64 { - if x != nil { - return x.UserId - } - return 0 -} - -func (x *SubjectCollectRequest) GetSubject() *Subject { - if x != nil { - return x.Subject - } - return nil -} - -func (x *SubjectCollectRequest) GetCollection() uint32 { - if x != nil { - return x.Collection - } - return 0 -} - -func (x *SubjectCollectRequest) GetComment() string { - if x != nil { - return x.Comment - } - return "" -} - -func (x *SubjectCollectRequest) GetRate() uint32 { - if x != nil { - return x.Rate - } - return 0 -} - -// 标记剧集为看过 -type EpisodeCollectRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - UserId uint64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - Last *Episode `protobuf:"bytes,2,opt,name=last,proto3" json:"last,omitempty"` - Subject *Subject `protobuf:"bytes,3,opt,name=subject,proto3" json:"subject,omitempty"` -} - -func (x *EpisodeCollectRequest) Reset() { - *x = EpisodeCollectRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_v1_timeline_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *EpisodeCollectRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*EpisodeCollectRequest) ProtoMessage() {} - -func (x *EpisodeCollectRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_timeline_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use EpisodeCollectRequest.ProtoReflect.Descriptor instead. -func (*EpisodeCollectRequest) Descriptor() ([]byte, []int) { - return file_api_v1_timeline_proto_rawDescGZIP(), []int{8} -} - -func (x *EpisodeCollectRequest) GetUserId() uint64 { - if x != nil { - return x.UserId - } - return 0 -} - -func (x *EpisodeCollectRequest) GetLast() *Episode { - if x != nil { - return x.Last - } - return nil -} - -func (x *EpisodeCollectRequest) GetSubject() *Subject { - if x != nil { - return x.Subject - } - return nil -} - -type SubjectProgressRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - UserId uint64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - Subject *Subject `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` - EpsUpdate uint32 `protobuf:"varint,3,opt,name=eps_update,json=epsUpdate,proto3" json:"eps_update,omitempty"` - VolsUpdate uint32 `protobuf:"varint,4,opt,name=vols_update,json=volsUpdate,proto3" json:"vols_update,omitempty"` -} - -func (x *SubjectProgressRequest) Reset() { - *x = SubjectProgressRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_v1_timeline_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SubjectProgressRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SubjectProgressRequest) ProtoMessage() {} - -func (x *SubjectProgressRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_timeline_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SubjectProgressRequest.ProtoReflect.Descriptor instead. -func (*SubjectProgressRequest) Descriptor() ([]byte, []int) { - return file_api_v1_timeline_proto_rawDescGZIP(), []int{9} -} - -func (x *SubjectProgressRequest) GetUserId() uint64 { - if x != nil { - return x.UserId - } - return 0 -} - -func (x *SubjectProgressRequest) GetSubject() *Subject { - if x != nil { - return x.Subject - } - return nil -} - -func (x *SubjectProgressRequest) GetEpsUpdate() uint32 { - if x != nil { - return x.EpsUpdate - } - return 0 -} - -func (x *SubjectProgressRequest) GetVolsUpdate() uint32 { - if x != nil { - return x.VolsUpdate - } - return 0 -} - -var File_api_v1_timeline_proto protoreflect.FileDescriptor - -var file_api_v1_timeline_proto_rawDesc = []byte{ - 0x0a, 0x15, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x22, - 0x22, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x22, 0x29, 0x0a, 0x0d, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x28, - 0x0a, 0x16, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x02, 0x6f, 0x6b, 0x22, 0x29, 0x0a, 0x17, 0x53, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x02, 0x6f, 0x6b, 0x22, 0x28, 0x0a, 0x16, 0x45, 0x70, 0x69, 0x73, 0x6f, 0x64, 0x65, 0x43, 0x6f, - 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, - 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x02, 0x6f, 0x6b, 0x22, 0xc4, 0x01, - 0x0a, 0x07, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x63, 0x6e, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x6e, 0x61, 0x6d, 0x65, 0x43, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6d, - 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, - 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x76, 0x6f, 0x6c, 0x73, - 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x76, 0x6f, - 0x6c, 0x73, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x70, 0x73, 0x5f, 0x74, - 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x65, 0x70, 0x73, 0x54, - 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x6e, 0x0a, 0x07, 0x45, 0x70, 0x69, 0x73, 0x6f, 0x64, 0x65, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, - 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x61, 0x6d, 0x65, 0x5f, - 0x63, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6e, 0x61, 0x6d, 0x65, 0x43, 0x6e, - 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x04, - 0x73, 0x6f, 0x72, 0x74, 0x22, 0xa9, 0x01, 0x0a, 0x15, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, - 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, - 0x72, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x72, 0x61, 0x74, 0x65, - 0x22, 0x80, 0x01, 0x0a, 0x15, 0x45, 0x70, 0x69, 0x73, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6c, 0x6c, - 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x75, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x04, 0x6c, 0x61, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x0f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x70, 0x69, 0x73, 0x6f, - 0x64, 0x65, 0x52, 0x04, 0x6c, 0x61, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x22, 0x9c, 0x01, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, - 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, - 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x70, 0x73, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x65, 0x70, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x76, 0x6f, 0x6c, 0x73, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x76, 0x6f, 0x6c, 0x73, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x32, 0xc5, 0x02, 0x0a, 0x0f, 0x54, 0x69, 0x6d, 0x65, 0x4c, 0x69, 0x6e, 0x65, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, - 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x48, - 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x51, - 0x0a, 0x0e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x12, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x54, 0x0a, 0x0f, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x72, 0x6f, 0x67, - 0x72, 0x65, 0x73, 0x73, 0x12, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x51, 0x0a, 0x0e, 0x45, 0x70, 0x69, 0x73, 0x6f, - 0x64, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x12, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x76, 0x31, 0x2e, 0x45, 0x70, 0x69, 0x73, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, - 0x31, 0x2e, 0x45, 0x70, 0x69, 0x73, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x1f, 0x5a, 0x1d, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x61, 0x6e, 0x67, 0x75, 0x6d, 0x69, - 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, -} - -var ( - file_api_v1_timeline_proto_rawDescOnce sync.Once - file_api_v1_timeline_proto_rawDescData = file_api_v1_timeline_proto_rawDesc -) - -func file_api_v1_timeline_proto_rawDescGZIP() []byte { - file_api_v1_timeline_proto_rawDescOnce.Do(func() { - file_api_v1_timeline_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_v1_timeline_proto_rawDescData) - }) - return file_api_v1_timeline_proto_rawDescData -} - -var file_api_v1_timeline_proto_msgTypes = make([]protoimpl.MessageInfo, 10) -var file_api_v1_timeline_proto_goTypes = []interface{}{ - (*HelloRequest)(nil), // 0: api.v1.HelloRequest - (*HelloResponse)(nil), // 1: api.v1.HelloResponse - (*SubjectCollectResponse)(nil), // 2: api.v1.SubjectCollectResponse - (*SubjectProgressResponse)(nil), // 3: api.v1.SubjectProgressResponse - (*EpisodeCollectResponse)(nil), // 4: api.v1.EpisodeCollectResponse - (*Subject)(nil), // 5: api.v1.Subject - (*Episode)(nil), // 6: api.v1.Episode - (*SubjectCollectRequest)(nil), // 7: api.v1.SubjectCollectRequest - (*EpisodeCollectRequest)(nil), // 8: api.v1.EpisodeCollectRequest - (*SubjectProgressRequest)(nil), // 9: api.v1.SubjectProgressRequest -} -var file_api_v1_timeline_proto_depIdxs = []int32{ - 5, // 0: api.v1.SubjectCollectRequest.subject:type_name -> api.v1.Subject - 6, // 1: api.v1.EpisodeCollectRequest.last:type_name -> api.v1.Episode - 5, // 2: api.v1.EpisodeCollectRequest.subject:type_name -> api.v1.Subject - 5, // 3: api.v1.SubjectProgressRequest.subject:type_name -> api.v1.Subject - 0, // 4: api.v1.TimeLineService.Hello:input_type -> api.v1.HelloRequest - 7, // 5: api.v1.TimeLineService.SubjectCollect:input_type -> api.v1.SubjectCollectRequest - 9, // 6: api.v1.TimeLineService.SubjectProgress:input_type -> api.v1.SubjectProgressRequest - 8, // 7: api.v1.TimeLineService.EpisodeCollect:input_type -> api.v1.EpisodeCollectRequest - 1, // 8: api.v1.TimeLineService.Hello:output_type -> api.v1.HelloResponse - 2, // 9: api.v1.TimeLineService.SubjectCollect:output_type -> api.v1.SubjectCollectResponse - 3, // 10: api.v1.TimeLineService.SubjectProgress:output_type -> api.v1.SubjectProgressResponse - 4, // 11: api.v1.TimeLineService.EpisodeCollect:output_type -> api.v1.EpisodeCollectResponse - 8, // [8:12] is the sub-list for method output_type - 4, // [4:8] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name -} - -func init() { file_api_v1_timeline_proto_init() } -func file_api_v1_timeline_proto_init() { - if File_api_v1_timeline_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_api_v1_timeline_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HelloRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_v1_timeline_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HelloResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_v1_timeline_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SubjectCollectResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_v1_timeline_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SubjectProgressResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_v1_timeline_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*EpisodeCollectResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_v1_timeline_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Subject); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_v1_timeline_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Episode); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_v1_timeline_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SubjectCollectRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_v1_timeline_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*EpisodeCollectRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_v1_timeline_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SubjectProgressRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_api_v1_timeline_proto_rawDesc, - NumEnums: 0, - NumMessages: 10, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_api_v1_timeline_proto_goTypes, - DependencyIndexes: file_api_v1_timeline_proto_depIdxs, - MessageInfos: file_api_v1_timeline_proto_msgTypes, - }.Build() - File_api_v1_timeline_proto = out.File - file_api_v1_timeline_proto_rawDesc = nil - file_api_v1_timeline_proto_goTypes = nil - file_api_v1_timeline_proto_depIdxs = nil -} diff --git a/generated/proto/go/api/v1/timeline_grpc.pb.go b/generated/proto/go/api/v1/timeline_grpc.pb.go deleted file mode 100644 index e41f6a4fa..000000000 --- a/generated/proto/go/api/v1/timeline_grpc.pb.go +++ /dev/null @@ -1,224 +0,0 @@ -// grpc server to create timeline - -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.3.0 -// - protoc (unknown) -// source: api/v1/timeline.proto - -package api - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 - -const ( - TimeLineService_Hello_FullMethodName = "/api.v1.TimeLineService/Hello" - TimeLineService_SubjectCollect_FullMethodName = "/api.v1.TimeLineService/SubjectCollect" - TimeLineService_SubjectProgress_FullMethodName = "/api.v1.TimeLineService/SubjectProgress" - TimeLineService_EpisodeCollect_FullMethodName = "/api.v1.TimeLineService/EpisodeCollect" -) - -// TimeLineServiceClient is the client API for TimeLineService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type TimeLineServiceClient interface { - // Debug function - Hello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) - SubjectCollect(ctx context.Context, in *SubjectCollectRequest, opts ...grpc.CallOption) (*SubjectCollectResponse, error) - SubjectProgress(ctx context.Context, in *SubjectProgressRequest, opts ...grpc.CallOption) (*SubjectProgressResponse, error) - EpisodeCollect(ctx context.Context, in *EpisodeCollectRequest, opts ...grpc.CallOption) (*EpisodeCollectResponse, error) -} - -type timeLineServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewTimeLineServiceClient(cc grpc.ClientConnInterface) TimeLineServiceClient { - return &timeLineServiceClient{cc} -} - -func (c *timeLineServiceClient) Hello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) { - out := new(HelloResponse) - err := c.cc.Invoke(ctx, TimeLineService_Hello_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *timeLineServiceClient) SubjectCollect(ctx context.Context, in *SubjectCollectRequest, opts ...grpc.CallOption) (*SubjectCollectResponse, error) { - out := new(SubjectCollectResponse) - err := c.cc.Invoke(ctx, TimeLineService_SubjectCollect_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *timeLineServiceClient) SubjectProgress(ctx context.Context, in *SubjectProgressRequest, opts ...grpc.CallOption) (*SubjectProgressResponse, error) { - out := new(SubjectProgressResponse) - err := c.cc.Invoke(ctx, TimeLineService_SubjectProgress_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *timeLineServiceClient) EpisodeCollect(ctx context.Context, in *EpisodeCollectRequest, opts ...grpc.CallOption) (*EpisodeCollectResponse, error) { - out := new(EpisodeCollectResponse) - err := c.cc.Invoke(ctx, TimeLineService_EpisodeCollect_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// TimeLineServiceServer is the server API for TimeLineService service. -// All implementations must embed UnimplementedTimeLineServiceServer -// for forward compatibility -type TimeLineServiceServer interface { - // Debug function - Hello(context.Context, *HelloRequest) (*HelloResponse, error) - SubjectCollect(context.Context, *SubjectCollectRequest) (*SubjectCollectResponse, error) - SubjectProgress(context.Context, *SubjectProgressRequest) (*SubjectProgressResponse, error) - EpisodeCollect(context.Context, *EpisodeCollectRequest) (*EpisodeCollectResponse, error) - mustEmbedUnimplementedTimeLineServiceServer() -} - -// UnimplementedTimeLineServiceServer must be embedded to have forward compatible implementations. -type UnimplementedTimeLineServiceServer struct { -} - -func (UnimplementedTimeLineServiceServer) Hello(context.Context, *HelloRequest) (*HelloResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Hello not implemented") -} -func (UnimplementedTimeLineServiceServer) SubjectCollect(context.Context, *SubjectCollectRequest) (*SubjectCollectResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method SubjectCollect not implemented") -} -func (UnimplementedTimeLineServiceServer) SubjectProgress(context.Context, *SubjectProgressRequest) (*SubjectProgressResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method SubjectProgress not implemented") -} -func (UnimplementedTimeLineServiceServer) EpisodeCollect(context.Context, *EpisodeCollectRequest) (*EpisodeCollectResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method EpisodeCollect not implemented") -} -func (UnimplementedTimeLineServiceServer) mustEmbedUnimplementedTimeLineServiceServer() {} - -// UnsafeTimeLineServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to TimeLineServiceServer will -// result in compilation errors. -type UnsafeTimeLineServiceServer interface { - mustEmbedUnimplementedTimeLineServiceServer() -} - -func RegisterTimeLineServiceServer(s grpc.ServiceRegistrar, srv TimeLineServiceServer) { - s.RegisterService(&TimeLineService_ServiceDesc, srv) -} - -func _TimeLineService_Hello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(HelloRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(TimeLineServiceServer).Hello(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: TimeLineService_Hello_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(TimeLineServiceServer).Hello(ctx, req.(*HelloRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _TimeLineService_SubjectCollect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(SubjectCollectRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(TimeLineServiceServer).SubjectCollect(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: TimeLineService_SubjectCollect_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(TimeLineServiceServer).SubjectCollect(ctx, req.(*SubjectCollectRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _TimeLineService_SubjectProgress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(SubjectProgressRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(TimeLineServiceServer).SubjectProgress(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: TimeLineService_SubjectProgress_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(TimeLineServiceServer).SubjectProgress(ctx, req.(*SubjectProgressRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _TimeLineService_EpisodeCollect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(EpisodeCollectRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(TimeLineServiceServer).EpisodeCollect(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: TimeLineService_EpisodeCollect_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(TimeLineServiceServer).EpisodeCollect(ctx, req.(*EpisodeCollectRequest)) - } - return interceptor(ctx, in, info, handler) -} - -// TimeLineService_ServiceDesc is the grpc.ServiceDesc for TimeLineService service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var TimeLineService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "api.v1.TimeLineService", - HandlerType: (*TimeLineServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "Hello", - Handler: _TimeLineService_Hello_Handler, - }, - { - MethodName: "SubjectCollect", - Handler: _TimeLineService_SubjectCollect_Handler, - }, - { - MethodName: "SubjectProgress", - Handler: _TimeLineService_SubjectProgress_Handler, - }, - { - MethodName: "EpisodeCollect", - Handler: _TimeLineService_EpisodeCollect_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "api/v1/timeline.proto", -} diff --git a/go.mod b/go.mod index fbdbd9d75..6666389c1 100644 --- a/go.mod +++ b/go.mod @@ -1,125 +1,130 @@ module github.com/bangumi/server -go 1.20 +go 1.26.3 + +tool github.com/vektra/mockery/v3 require ( - github.com/avast/retry-go/v4 v4.3.3 - github.com/davecgh/go-spew v1.1.1 - github.com/elliotchance/phpserialize v1.3.3 + github.com/avast/retry-go/v5 v5.0.0 + github.com/aws/aws-sdk-go-v2 v1.41.7 + github.com/aws/aws-sdk-go-v2/credentials v1.19.16 + github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0 + github.com/bangumi/wiki-parser-go v0.0.2 + github.com/bytedance/sonic v1.15.1 + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/go-playground/locales v0.14.1 github.com/go-playground/universal-translator v0.18.1 - github.com/go-playground/validator/v10 v10.12.0 - github.com/go-redis/redismock/v9 v9.0.2 - github.com/go-resty/resty/v2 v2.7.0 - github.com/go-sql-driver/mysql v1.7.0 - github.com/ilyakaznacheev/cleanenv v1.4.2 - github.com/jarcoal/httpmock v1.3.0 - github.com/labstack/echo/v4 v4.10.2 - github.com/mattn/go-colorable v0.1.13 - github.com/meilisearch/meilisearch-go v0.24.0 - github.com/minio/minio-go/v7 v7.0.51 + github.com/go-playground/validator/v10 v10.30.2 + github.com/go-resty/resty/v2 v2.17.2 + github.com/go-sql-driver/mysql v1.10.0 + github.com/google/uuid v1.6.0 + github.com/ilyakaznacheev/cleanenv v1.5.0 + github.com/jarcoal/httpmock v1.4.1 + github.com/jmoiron/sqlx v1.4.0 + github.com/labstack/echo/v5 v5.1.1 + github.com/mattn/go-colorable v0.1.14 + github.com/meilisearch/meilisearch-go v0.36.2 github.com/mitchellh/mapstructure v1.5.0 - github.com/prometheus/client_golang v1.14.0 - github.com/redis/go-redis/v9 v9.0.3 - github.com/samber/lo v1.38.1 - github.com/segmentio/kafka-go v0.4.39 - github.com/spf13/cobra v1.7.0 - github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.8.2 - github.com/trim21/errgo v0.0.2 - github.com/trim21/go-phpserialize v0.0.18 - github.com/trim21/go-redis-prometheus v0.0.0 - github.com/trim21/htest v0.0.3 - github.com/trim21/pkg v0.0.3 - github.com/vektra/mockery/v2 v2.23.2 - go.etcd.io/etcd/client/v3 v3.5.7 - go.uber.org/fx v1.19.2 - go.uber.org/zap v1.24.0 - golang.org/x/crypto v0.8.0 - google.golang.org/grpc v1.52.3 - google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 - google.golang.org/protobuf v1.30.0 - gopkg.in/yaml.v3 v3.0.1 - gorm.io/driver/mysql v1.4.7 - gorm.io/gen v0.3.21 - gorm.io/gorm v1.24.6 - gorm.io/plugin/dbresolver v1.4.1 + github.com/prometheus/client_golang v1.23.2 + github.com/redis/rueidis v1.0.74 + github.com/samber/lo v1.53.0 + github.com/segmentio/kafka-go v0.4.51 + github.com/spf13/cobra v1.10.2 + github.com/spf13/pflag v1.0.10 + github.com/stretchr/testify v1.11.1 + github.com/trim21/errgo v0.0.6 + github.com/trim21/go-phpserialize v0.1.2 + github.com/trim21/htest v0.0.4 + go.uber.org/fx v1.24.0 + go.uber.org/zap v1.28.0 + golang.org/x/sync v0.20.0 + golang.org/x/text v0.37.0 + gorm.io/driver/mysql v1.6.0 + gorm.io/gen v0.3.27 + gorm.io/gorm v1.31.1 + gorm.io/plugin/dbresolver v1.6.2 + gorm.io/plugin/soft_delete v1.2.1 ) require ( - github.com/BurntSushi/toml v1.1.0 // indirect - github.com/andybalholm/brotli v1.0.4 // indirect + filippo.io/edwards25519 v1.2.0 // indirect + github.com/BurntSushi/toml v1.5.0 // indirect + github.com/andybalholm/brotli v1.1.1 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 // indirect + github.com/aws/smithy-go v1.25.1 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/chigopher/pathlib v0.12.0 // indirect - github.com/coreos/go-semver v0.3.0 // indirect - github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 // indirect - github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt v3.2.2+incompatible // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/iancoleman/strcase v0.2.0 // indirect + github.com/brunoga/deep v1.3.1 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic/loader v0.5.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/clipperhouse/uax29/v2 v2.2.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/fatih/structs v1.1.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect + github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jinzhu/copier v0.3.5 // indirect + github.com/jedib0t/go-pretty/v6 v6.7.8 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/joho/godotenv v1.4.0 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.16.0 // indirect - github.com/klauspost/cpuid/v2 v2.2.4 // indirect - github.com/labstack/gommon v0.4.0 // indirect - github.com/leodido/go-urn v1.2.2 // indirect - github.com/magiconair/properties v1.8.7 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/minio/md5-simd v1.1.2 // indirect - github.com/minio/sha256-simd v1.0.0 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.0.6 // indirect - github.com/pierrec/lz4/v4 v4.1.15 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.37.0 // indirect - github.com/prometheus/procfs v0.8.0 // indirect - github.com/rs/xid v1.4.0 // indirect - github.com/rs/zerolog v1.29.0 // indirect - github.com/sirupsen/logrus v1.9.0 // indirect - github.com/spf13/afero v1.9.3 // indirect - github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/viper v1.15.0 // indirect - github.com/stretchr/objx v0.5.0 // indirect - github.com/subosito/gotenv v1.4.2 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/knadh/koanf/maps v0.1.2 // indirect + github.com/knadh/koanf/parsers/yaml v1.1.0 // indirect + github.com/knadh/koanf/providers/env v1.1.0 // indirect + github.com/knadh/koanf/providers/file v1.2.1 // indirect + github.com/knadh/koanf/providers/posflag v1.0.1 // indirect + github.com/knadh/koanf/providers/structs v1.0.0 // indirect + github.com/knadh/koanf/v2 v2.3.2 // indirect + github.com/labstack/echo/v4 v4.15.1 // indirect + github.com/labstack/gommon v0.4.2 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.19 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pierrec/lz4/v4 v4.1.22 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + github.com/rs/zerolog v1.34.0 // indirect + github.com/stretchr/objx v0.5.3 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.37.1-0.20220607072126-8a320890c08d // indirect github.com/valyala/fasttemplate v1.2.2 // indirect - go.etcd.io/etcd/api/v3 v3.5.7 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/dig v1.16.1 // indirect - go.uber.org/multierr v1.8.0 // indirect - golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect - golang.org/x/mod v0.8.0 // indirect - golang.org/x/net v0.9.0 // indirect - golang.org/x/sys v0.7.0 // indirect - golang.org/x/term v0.7.0 // indirect - golang.org/x/text v0.9.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.6.0 // indirect - google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect - gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gorm.io/datatypes v1.0.7 // indirect - gorm.io/hints v1.1.0 // indirect + github.com/vektra/mockery/v3 v3.7.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + go.uber.org/dig v1.19.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 // indirect + golang.org/x/arch v0.17.0 // indirect + golang.org/x/crypto v0.50.0 // indirect + golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/net v0.53.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/term v0.42.0 // indirect + golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.44.0 // indirect + google.golang.org/protobuf v1.36.8 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/datatypes v1.2.5 // indirect + gorm.io/hints v1.1.2 // indirect olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect ) diff --git a/go.sum b/go.sum index e983f69c9..4c4111bd7 100644 --- a/go.sum +++ b/go.sum @@ -1,1160 +1,334 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= -github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= -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/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= -github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= -github.com/avast/retry-go/v4 v4.3.3 h1:G56Bp6mU0b5HE1SkaoVjscZjlQb0oy4mezwY/cGH19w= -github.com/avast/retry-go/v4 v4.3.3/go.mod h1:rg6XFaiuFYII0Xu3RDbZQkxCofFwruZKW8oEF1jpWiU= -github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= -github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -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= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= +filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/avast/retry-go/v5 v5.0.0 h1:kf1Qc2UsTZ4qq8elDymqfbISvkyMuhgRxuJqX2NHP7k= +github.com/avast/retry-go/v5 v5.0.0/go.mod h1://d+usmKWio1agtZfS1H/ltTqwtIfBnRq9zEwjc3eH8= +github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8= +github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 h1:gx1AwW1Iyk9Z9dD9F4akX5gnN3QZwUB20GGKH/I+Rho= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10/go.mod h1:qqY157uZoqm5OXq/amuaBJyC9hgBCBQnsaWnPe905GY= +github.com/aws/aws-sdk-go-v2/credentials v1.19.16 h1:r3RJBuU7X9ibt8RHbMjWE6y60QbKBiII6wSrXnapxSU= +github.com/aws/aws-sdk-go-v2/credentials v1.19.16/go.mod h1:6cx7zqDENJDbBIIWX6P8s0h6hqHC8Avbjh9Dseo27ug= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23/go.mod h1:15DfR2nw+CRHIk0tqNyifu3G1YdAOy68RftkhMDDwYk= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24/go.mod h1:X5ZJyfwVrWA96GzPmUCWFQaEARPR7gCrpq2E92PJwAE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZLt5ci0ozzgkVo8BJGwvqNaZbTWb3UcucAateA= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9/go.mod h1:w7wZ/s9qK7c8g4al+UyoF1Sp/Z45UwMGcqIzLWVQHWk= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 h1:ieLCO1JxUWuxTZ1cRd0GAaeX7O6cIxnwk7tc1LsQhC4= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15/go.mod h1:e3IzZvQ3kAWNykvE0Tr0RDZCMFInMvhku3qNpcIQXhM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 h1:pbrxO/kuIwgEsOPLkaHu0O+m4fNgLU8B3vxQ+72jTPw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23/go.mod h1:/CMNUqoj46HpS3MNRDEDIwcgEnrtZlKRaHNaHxIFpNA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 h1:03xatSQO4+AM1lTAbnRg5OK528EUg744nW7F73U8DKw= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23/go.mod h1:M8l3mwgx5ToK7wot2sBBce/ojzgnPzZXUV445gTSyE8= +github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0 h1:etqBTKY581iwLL/H/S2sVgk3C9lAsTJFeXWFDsDcWOU= +github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0/go.mod h1:L2dcoOgS2VSgbPLvpak2NyUPsO1TBN7M45Z4H7DlRc4= +github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI= +github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/bangumi/wiki-parser-go v0.0.2 h1:VSzIu/3CpgHBjT0RtE9aM4RGzRORNLM20CL0CuxyRLs= +github.com/bangumi/wiki-parser-go v0.0.2/go.mod h1:ELlLuMFhEUuLnySpJse2B/9RCeYcdogJOjvWZNbfIiA= 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/bsm/ginkgo/v2 v2.5.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= -github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao= -github.com/bsm/gomega v1.20.0/go.mod h1:JifAceMQ4crZIWYUKrlGcmbN3bqHogVTADMD2ATsbwk= -github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= -github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chigopher/pathlib v0.12.0 h1:1GM7fN/IwXXmOHbd1jkMqHD2wUhYqUvafgxTwmLT/q8= -github.com/chigopher/pathlib v0.12.0/go.mod h1:EJ5UtJ/sK8Nt6q3VWN+EwZLZ3g0afJiG8NegYiQQ/gQ= -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/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/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.3.3-0.20220203105225-a9a7ef127534 h1:rtAn27wIbmOGUs7RIbVgPEjb31ehTVniDwPGXyMxm5U= -github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -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.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= +github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= +github.com/brunoga/deep v1.3.1 h1:bSrL6FhAZa6JlVv4vsi7Hg8SLwroDb1kgDERRVipBCo= +github.com/brunoga/deep v1.3.1/go.mod h1:GDV6dnXqn80ezsLSZ5Wlv1PdKAWAO4L5PnKYtv2dgaI= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.15.1 h1:nJD5PmM0vY7J8CT6MxoqbVAAMhkSmV2HgRAUrrpLoOw= +github.com/bytedance/sonic v1.15.1/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA= +github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI= +github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY= +github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 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.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU= -github.com/denisenkom/go-mssqldb v0.12.2 h1:1OcPn5GBIobjWNd+8yjfHNIaFX14B1pWI3F9HZy5KXw= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/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= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/elliotchance/phpserialize v1.3.3 h1:hV4QVmGdCiYgoBbw+ADt6fNgyZ2mYX0OgpnON1adTCM= -github.com/elliotchance/phpserialize v1.3.3/go.mod h1:gt7XX9+ETUcLXbtTKEuyrqW3lcLUAeS/AnGZ2e49TZs= -github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= -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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= -github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= -github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= -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-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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI= -github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA= -github.com/go-redis/redismock/v9 v9.0.2 h1:1X51FovN18M9GXBdbi5xWiXoFPXAijdLdvA7VrYjoVA= -github.com/go-redis/redismock/v9 v9.0.2/go.mod h1:Ojrqw2Kut8BB8HZlXwNgfwhp5xvtVQTjgbIdIMi980g= -github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= -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.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= -github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ= +github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc= +github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk= +github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw= +github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= -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-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/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/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.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/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/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/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v1.6.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/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= -github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.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-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 v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/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/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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= -github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= -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/ilyakaznacheev/cleanenv v1.4.2 h1:nRqiriLMAC7tz7GzjzUTBHfzdzw6SQ7XvTagkFqe/zU= -github.com/ilyakaznacheev/cleanenv v1.4.2/go.mod h1:i0owW+HDxeGKE0/JPREJOdSCPIyOnmh6C0xhWAkF/xA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4= +github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys= -github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw= -github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E= -github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= -github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= -github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= -github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A= +github.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0= +github.com/jedib0t/go-pretty/v6 v6.7.8 h1:BVYrDy5DPBA3Qn9ICT+PokP9cvCv1KaHv2i+Hc8sr5o= +github.com/jedib0t/go-pretty/v6 v6.7.8/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= -github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.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.8/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= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/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/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -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.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -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.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo= +github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= +github.com/knadh/koanf/parsers/yaml v1.1.0 h1:3ltfm9ljprAHt4jxgeYLlFPmUaunuCgu1yILuTXRdM4= +github.com/knadh/koanf/parsers/yaml v1.1.0/go.mod h1:HHmcHXUrp9cOPcuC+2wrr44GTUB0EC+PyfN3HZD9tFg= +github.com/knadh/koanf/providers/env v1.1.0 h1:U2VXPY0f+CsNDkvdsG8GcsnK4ah85WwWyJgef9oQMSc= +github.com/knadh/koanf/providers/env v1.1.0/go.mod h1:QhHHHZ87h9JxJAn2czdEl6pdkNnDh/JS1Vtsyt65hTY= +github.com/knadh/koanf/providers/file v1.2.1 h1:bEWbtQwYrA+W2DtdBrQWyXqJaJSG3KrP3AESOJYp9wM= +github.com/knadh/koanf/providers/file v1.2.1/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA= +github.com/knadh/koanf/providers/posflag v1.0.1 h1:EnMxHSrPkYCFnKgBUl5KBgrjed8gVFrcXDzaW4l/C6Y= +github.com/knadh/koanf/providers/posflag v1.0.1/go.mod h1:3Wn3+YG3f4ljzRyCUgIwH7G0sZ1pMjCOsNBovrbKmAk= +github.com/knadh/koanf/providers/structs v1.0.0 h1:DznjB7NQykhqCar2LvNug3MuxEQsZ5KvfgMbio+23u4= +github.com/knadh/koanf/providers/structs v1.0.0/go.mod h1:kjo5TFtgpaZORlpoJqcbeLowM2cINodv8kX+oFAeQ1w= +github.com/knadh/koanf/v2 v2.3.2 h1:Ee6tuzQYFwcZXQpc2MiVeC6qHMandf5SMUJJNoFp/c4= +github.com/knadh/koanf/v2 v2.3.2/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= -github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= -github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= -github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= -github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4= -github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= -github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.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.6/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/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/labstack/echo/v4 v4.15.1 h1:S9keusg26gZpjMmPqB5hOEvNKnmd1lNmcHrbbH2lnFs= +github.com/labstack/echo/v4 v4.15.1/go.mod h1:xmw1clThob0BSVRX1CRQkGQ/vjwcpOMjQZSZa9fKA/c= +github.com/labstack/echo/v5 v5.1.1 h1:4QkvKoS8ps5ch49t8b72QS9Z581ytgxhTzxuB/CBA2I= +github.com/labstack/echo/v5 v5.1.1/go.mod h1:SyvlSdObGjRXeQfCCXW/sybkZdOOQZBmpKF0bvALaeo= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -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.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-sqlite3 v1.14.8/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.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= -github.com/meilisearch/meilisearch-go v0.24.0 h1:GTP8LWZmkMYrGgX5BRZdkC2Txyp0mFYLzXYMlVV7cSQ= -github.com/meilisearch/meilisearch-go v0.24.0/go.mod h1:SxuSqDcPBIykjWz1PX+KzsYzArNLSCadQodWs8extS0= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -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.51 h1:eSewrwc23TqUDEH8aw8Bwp4f+JDdozRrPWcKR7DZhmY= -github.com/minio/minio-go/v7 v7.0.51/go.mod h1:IbbodHyjUAguneyucUaahv+VMNs/EOTV9du7A7/Z3HU= -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/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 v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -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 v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= +github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/maxatome/go-testdeep v1.14.0 h1:rRlLv1+kI8eOI3OaBXZwb3O7xY3exRzdW5QyX48g9wI= +github.com/maxatome/go-testdeep v1.14.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= +github.com/meilisearch/meilisearch-go v0.36.2 h1:MYaMPCpdLh2aYPt+zK+19mLoA4dfBY3S1L7T0FADCjU= +github.com/meilisearch/meilisearch-go v0.36.2/go.mod h1:hWcR0MuWLSzHfbz9GGzIr3s9rnXLm1jqkmHkJPbUSvM= +github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA= +github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= -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/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= -github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= -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/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -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.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y= -github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= -github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= -github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= -github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= -github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= -github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= -github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/gomega v1.38.3 h1:eTX+W6dobAYfFeGC2PV6RwXRu/MyT+cQguijutvkpSM= +github.com/onsi/gomega v1.38.3/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= +github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= +github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= -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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0/7TSWwj+ITvv0TnM= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/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.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/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/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps= -github.com/redis/go-redis/v9 v9.0.3 h1:+7mmR26M0IvyLxGZUHxu4GiBkJkVDid0Un+j4ScYu4k= -github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= -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/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w= -github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/redis/rueidis v1.0.74 h1:J5ZNyxMqX+sDQxQztRI928W6TrERpo+pHSwhftnX7NA= +github.com/redis/rueidis v1.0.74/go.mod h1:lfdcZzJ1oKGKL37vh9fO3ymwt+0TdjkkUCJxbgpmcgQ= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= -github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= -github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/segmentio/kafka-go v0.4.39 h1:75smaomhvkYRwtuOwqLsdhgCG30B82NsbdkdDfFbvrw= -github.com/segmentio/kafka-go v0.4.39/go.mod h1:T0MLgygYvmqmBvC+s8aCcbVNfJN4znVne5j0Pzowp/Q= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= -github.com/spf13/afero v1.4.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= -github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.1/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.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= -github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= -github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM= +github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= +github.com/segmentio/kafka-go v0.4.51 h1:JgDPPG75tC1rWIS2Me6MwcvXJ6f49UQ4HjAOef71Hno= +github.com/segmentio/kafka-go v0.4.51/go.mod h1:Y1gn60kzLEEaW28YshXyk2+VCUKbJ3Qr6DrnT3i4+9E= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= -github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/trim21/errgo v0.0.2 h1:OlaTR0PaSzBGFgBMEhwwTtnpN2q03WNNr3YossUCHCY= -github.com/trim21/errgo v0.0.2/go.mod h1:T9MN4yD51VA68yqmod0e6z4ZjTBd8buD0aCFjfQ+hUM= -github.com/trim21/go-phpserialize v0.0.18 h1:rEGoaQA6j9kXuYcPx+SFwL+2epyCjNtCfhUM61YOip0= -github.com/trim21/go-phpserialize v0.0.18/go.mod h1:LQI8ZDQRM7YToxzXCaHwV+vl98A1nddrd7yi9OZOkWQ= -github.com/trim21/go-redis-prometheus v0.0.0 h1:9svVIZkKaDGE1bSRbtTQdsKBzW+QEWfwc35QIF8JsfA= -github.com/trim21/go-redis-prometheus v0.0.0/go.mod h1:UTXPI/fofnsXF9/X/WtvwhAdbSOBVjLg17xDjQj9VgU= -github.com/trim21/htest v0.0.3 h1:1y3qw/vdeNyTlV5kZ/xCFLqo94eyC2mKmItaf60NaHA= -github.com/trim21/htest v0.0.3/go.mod h1:c1SxijYYVv4b8XUoT02xgZhaEJtfqduubO9OEPRIPEg= -github.com/trim21/pkg v0.0.3 h1:uAqfoFmmYiIMOSretKj8/tvrQs3KG57020Ff0cx8UtE= -github.com/trim21/pkg v0.0.3/go.mod h1:JrRIFidkCLeuU5j0vBP5ZN0NOp2JavagHZNr4D3AH6Q= -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/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/trim21/errgo v0.0.6 h1:Qz10hGY51k5u7ADRa/jqan8VaQYKx7hAqUJ6t5m42/I= +github.com/trim21/errgo v0.0.6/go.mod h1:8uSKeZAGc+XDaPDqcgOCduVPb2Xpb1YxqxfMK1JcLlo= +github.com/trim21/go-phpserialize v0.1.2 h1:pVkA5n4IeUxpJemGmCLZXFhnZs5wL6LCqofi++zMqDU= +github.com/trim21/go-phpserialize v0.1.2/go.mod h1:StH8iTviDvvY7dcMSNgRSzRzcuKmj2YZ/EETVRGlIws= +github.com/trim21/htest v0.0.4 h1:dDIzKNdIClgtB158DlO+Xf0sfwNycmx3kfo/FJuY+eE= +github.com/trim21/htest v0.0.4/go.mod h1:W+zaYAGCBqx38eMrMGvXrALnbcXR6OBtZiRiHahgo+E= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.37.1-0.20220607072126-8a320890c08d h1:xS9QTPgKl9ewGsAOPc+xW7DeStJDqYPfisDmeSCcbco= -github.com/valyala/fasthttp v1.37.1-0.20220607072126-8a320890c08d/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -github.com/vektra/mockery/v2 v2.23.2 h1:SEqsPdKBQUuDVNumY/z15UVEHLZIahFLQpE73nKdQv4= -github.com/vektra/mockery/v2 v2.23.2/go.mod h1:Zh3Kv1ckKs6FokhlVLcCu6UTyzfS3M8mpROz1lBNp+w= -github.com/volatiletech/null/v9 v9.0.0 h1:JCdlHEiSRVxOi7/MABiEfdsqmuj9oTV20Ao7VvZ0JkE= -github.com/xdg/scram v1.0.5 h1:TuS0RFmt5Is5qm9Tm2SoD89OPqe4IRiFtyFY4iwWXsw= -github.com/xdg/scram v1.0.5/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= -github.com/xdg/stringprep v1.0.3 h1:cmL5Enob4W83ti/ZHuZLuKD/xqJfus4fVPwE+/BDm+4= -github.com/xdg/stringprep v1.0.3/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -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/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.etcd.io/etcd/api/v3 v3.5.7 h1:sbcmosSVesNrWOJ58ZQFitHMdncusIifYcrBfwrlJSY= -go.etcd.io/etcd/api/v3 v3.5.7/go.mod h1:9qew1gCdDDLu+VwmeG+iFpL+QlpHTo7iubavdVDgCAA= -go.etcd.io/etcd/client/pkg/v3 v3.5.7 h1:y3kf5Gbp4e4q7egZdn5T7W9TSHUvkClN6u+Rq9mEOmg= -go.etcd.io/etcd/client/pkg/v3 v3.5.7/go.mod h1:o0Abi1MK86iad3YrWhgUsbGx1pmTS+hrORWc2CamuhY= -go.etcd.io/etcd/client/v3 v3.5.7 h1:u/OhpiuCgYY8awOHlhIhmGIGpxfBU/GZBUP3m/3/Iz4= -go.etcd.io/etcd/client/v3 v3.5.7/go.mod h1:sOWmj9DZUMyAngS7QQwCyAXXAL6WhgTOPLNS/NabQgw= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/dig v1.16.1 h1:+alNIBsl0qfY0j6epRubp/9obgtrObRAc5aD+6jbWY8= -go.uber.org/dig v1.16.1/go.mod h1:557JTAUZT5bUK0SvCwikmLPPtdQhfvLYtO5tJgQSbnk= -go.uber.org/fx v1.19.2 h1:SyFgYQFr1Wl0AYstE8vyYIzP4bFz2URrScjwC4cwUvY= -go.uber.org/fx v1.19.2/go.mod h1:43G1VcqSzbIv77y00p1DRAsyZS8WdzuYdhZXmEUkMyQ= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= -golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= -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/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -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-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-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-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/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-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-20220706163947-c90051bbdb60/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-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-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/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-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/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-20210603081109-ebe580a85c40/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-20210630005230-0f9fa26af87c/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-20211103235746-7861aae1554b/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-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +github.com/vektra/mockery/v3 v3.7.0 h1:Dd0EeaOcRJBVP9n3oYOVPV7KdPaaE3EcwTppaZIsFSM= +github.com/vektra/mockery/v3 v3.7.0/go.mod h1:z9Wr23Ha8etImqQwS3boTNR9WkjX6tIklW5c88DRkSw= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4= +go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= +go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg= +go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 h1:lGdhQUN/cnWdSH3291CUuxSEqc+AsGTiDxPP3r2J0l4= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= +golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU= +golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05ST2uO1exVfZPVqRC5o= +golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= -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= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -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.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/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-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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.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.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/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.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/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= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/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-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -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.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= -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.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -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.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.52.3 h1:pf7sOysg4LdgBqduXveGKrcEwbStiK2rtfghdzlUYDQ= -google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 h1:rNBFJjBCOgVr9pWD7rs/knKL4FRTKgpZmsRfV214zcA= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0/go.mod h1:Dk1tviKTvMCz5tvh7t+fh94dhmQVHuCt2OzJB3CTW9Y= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -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/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -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.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.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/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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/datatypes v1.0.7 h1:8NhJN4+annFjwV1WufDhFiPjdUvV1lSGUdg1UCjQIWY= -gorm.io/datatypes v1.0.7/go.mod h1:l9qkCuy0CdzDEop9HKUdcnC9gHC2sRlaFtHkTzsZRqg= -gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U= -gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= -gorm.io/driver/mysql v1.4.7 h1:rY46lkCspzGHn7+IYsNpSfEv9tA+SU4SkkB+GFX125Y= -gorm.io/driver/mysql v1.4.7/go.mod h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8oc= -gorm.io/driver/postgres v1.3.4/go.mod h1:y0vEuInFKJtijuSGu9e5bs5hzzSzPK+LancpKpvbRBw= -gorm.io/driver/postgres v1.4.1 h1:DutsKq2LK2Ag65q/+VygWth0/L4GAVOp+sCtg6WzZjs= -gorm.io/driver/sqlite v1.1.6/go.mod h1:W8LmC/6UvVbHKah0+QOC7Ja66EaZXHwUTjgXY8YNWX8= -gorm.io/driver/sqlite v1.3.1/go.mod h1:wJx0hJspfycZ6myN38x1O/AqLtNS6c5o9TndewFbELg= -gorm.io/driver/sqlite v1.4.1 h1:ThZ3dRIbTbWGvaMHSVjgf0sb6SRJMNRyQAwfLo25+cM= -gorm.io/driver/sqlserver v1.3.1/go.mod h1:w25Vrx2BG+CJNUu/xKbFhaKlGxT/nzRkhWCCoptX8tQ= -gorm.io/driver/sqlserver v1.4.0 h1:3fjbsNkr/YqocSBW5CP16Lq6+APjRrWMzu7NbkXr9QU= -gorm.io/gen v0.3.21 h1:t8329wT4tW1ZZWOm7vn4LV6OIrz8a5zCg+p78ezt+rA= -gorm.io/gen v0.3.21/go.mod h1:aWgvoKdG9f8Des4TegSa0N5a+gwhGsFo0JJMaLwokvk= -gorm.io/gorm v1.21.15/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= -gorm.io/gorm v1.22.2/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= -gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= -gorm.io/gorm v1.23.6/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= -gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= -gorm.io/gorm v1.24.3/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= -gorm.io/gorm v1.24.6 h1:wy98aq9oFEetsc4CAbKD2SoBCdMzsbSIvSUUFJuHi5s= -gorm.io/gorm v1.24.6/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= -gorm.io/hints v1.1.0 h1:Lp4z3rxREufSdxn4qmkK3TLDltrM10FLTHiuqwDPvXw= -gorm.io/hints v1.1.0/go.mod h1:lKQ0JjySsPBj3uslFzY3JhYDtqEwzm+G1hv8rWujB6Y= -gorm.io/plugin/dbresolver v1.4.1 h1:Ug4LcoPhrvqq71UhxtF346f+skTYoCa/nEsdjvHwEzk= -gorm.io/plugin/dbresolver v1.4.1/go.mod h1:CTbCtMWhsjXSiJqiW2R8POvJ2cq18RVOl4WGyT5nhNc= -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= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +gorm.io/datatypes v1.2.5 h1:9UogU3jkydFVW1bIVVeoYsTpLRgwDVW3rHfJG6/Ek9I= +gorm.io/datatypes v1.2.5/go.mod h1:I5FUdlKpLb5PMqeMQhm30CQ6jXP8Rj89xkTeCSAaAD4= +gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= +gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= +gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U= +gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A= +gorm.io/driver/sqlite v1.1.3/go.mod h1:AKDgRWk8lcSQSw+9kxCJnX/yySj8G3rdwYlU57cB45c= +gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I= +gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= +gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= +gorm.io/driver/sqlserver v1.5.4 h1:xA+Y1KDNspv79q43bPyjDMUgHoYHLhXYmdFcYPobg8g= +gorm.io/driver/sqlserver v1.5.4/go.mod h1:+frZ/qYmuna11zHPlh5oc2O6ZA/lS88Keb0XSH1Zh/g= +gorm.io/gen v0.3.27 h1:ziocAFLpE7e0g4Rum69pGfB9S6DweTxK8gAun7cU8as= +gorm.io/gen v0.3.27/go.mod h1:9zquz2xD1f3Eb/eHq4oLn2z6vDVvQlCY5S3uMBLv4EA= +gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.23.0/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= +gorm.io/hints v1.1.2 h1:b5j0kwk5p4+3BtDtYqqfY+ATSxjj+6ptPgVveuynn9o= +gorm.io/hints v1.1.2/go.mod h1:/ARdpUHAtyEMCh5NNi3tI7FsGh+Cj/MIUlvNxCNCFWg= +gorm.io/plugin/dbresolver v1.6.2 h1:F4b85TenghUeITqe3+epPSUtHH7RIk3fXr5l83DF8Pc= +gorm.io/plugin/dbresolver v1.6.2/go.mod h1:tctw63jdrOezFR9HmrKnPkmig3m5Edem9fdxk9bQSzM= +gorm.io/plugin/soft_delete v1.2.1 h1:qx9D/c4Xu6w5KT8LviX8DgLcB9hkKl6JC9f44Tj7cGU= +gorm.io/plugin/soft_delete v1.2.1/go.mod h1:Zv7vQctOJTGOsJ/bWgrN1n3od0GBAZgnLjEx+cApLGk= olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ= olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/internal/auth/domain.go b/internal/auth/domain.go index 78fed50ea..228e84b04 100644 --- a/internal/auth/domain.go +++ b/internal/auth/domain.go @@ -28,41 +28,17 @@ type Repo interface { // GetByToken return an authorized user by a valid access token. GetByToken(ctx context.Context, token string) (UserInfo, error) GetPermission(ctx context.Context, groupID uint8) (Permission, error) - - CreateAccessToken( - ctx context.Context, userID model.UserID, name string, expiration time.Duration, - ) (token string, err error) - - ListAccessToken(ctx context.Context, userID model.UserID) ([]AccessToken, error) - DeleteAccessToken(ctx context.Context, tokenID uint32) (bool, error) - - // GetByEmail return (Auth, HashedPassword, error) - GetByEmail(ctx context.Context, email string) (UserInfo, []byte, error) - GetTokenByID(ctx context.Context, id uint32) (AccessToken, error) } type Service interface { GetByToken(ctx context.Context, token string) (Auth, error) - GetByID(ctx context.Context, userID model.UserID) (Auth, error) - - ComparePassword(hashed []byte, password string) (bool, error) - - Login(ctx context.Context, email, password string) (Auth, bool, error) - - GetTokenByID(ctx context.Context, tokenID uint32) (AccessToken, error) - CreateAccessToken( - ctx context.Context, userID model.UserID, name string, expiration time.Duration, - ) (token string, err error) - ListAccessToken(ctx context.Context, userID model.UserID) ([]AccessToken, error) - DeleteAccessToken(ctx context.Context, tokenID uint32) (bool, error) - - // GetPermission(ctx context.Context, id model.UserGroupID) (Permission, error) } type UserInfo struct { - RegTime time.Time - ID model.UserID - GroupID user.GroupID + RegTime time.Time + ID model.UserID + GroupID user.GroupID + Permission Permission } // Auth is the basic authorization represent a user. @@ -71,7 +47,7 @@ type Auth struct { RegTime time.Time ID model.UserID // user id GroupID user.GroupID - Permission Permission `json:"-"` // disable cache for this field. + Permission Permission } const nsfwThreshold = gtime.OneDay * 60 @@ -133,3 +109,42 @@ type Permission struct { ManageApp bool AppErase bool } + +func (p Permission) Merge(other Permission) Permission { + return Permission{ + UserBan: p.UserBan || other.UserBan, + BanPost: p.BanPost || other.BanPost, + + UserList: p.UserList && other.UserList, + ManageUserGroup: p.ManageUserGroup && other.ManageUserGroup, + ManageUserPhoto: p.ManageUserPhoto && other.ManageUserPhoto, + ManageTopicState: p.ManageTopicState && other.ManageTopicState, + ManageReport: p.ManageReport && other.ManageReport, + ManageUser: p.ManageUser && other.ManageUser, + UserGroup: p.UserGroup && other.UserGroup, + UserWikiApply: p.UserWikiApply && other.UserWikiApply, + UserWikiApprove: p.UserWikiApprove && other.UserWikiApprove, + DoujinSubjectErase: p.DoujinSubjectErase && other.DoujinSubjectErase, + DoujinSubjectLock: p.DoujinSubjectLock && other.DoujinSubjectLock, + SubjectEdit: p.SubjectEdit && other.SubjectEdit, + SubjectLock: p.SubjectLock && other.SubjectLock, + SubjectRefresh: p.SubjectRefresh && other.SubjectRefresh, + SubjectRelated: p.SubjectRelated && other.SubjectRelated, + SubjectMerge: p.SubjectMerge && other.SubjectMerge, + SubjectErase: p.SubjectErase && other.SubjectErase, + SubjectCoverLock: p.SubjectCoverLock && other.SubjectCoverLock, + SubjectCoverErase: p.SubjectCoverErase && other.SubjectCoverErase, + MonoEdit: p.MonoEdit && other.MonoEdit, + MonoLock: p.MonoLock && other.MonoLock, + MonoMerge: p.MonoMerge && other.MonoMerge, + MonoErase: p.MonoErase && other.MonoErase, + EpEdit: p.EpEdit && other.EpEdit, + EpMove: p.EpMove && other.EpMove, + EpMerge: p.EpMerge && other.EpMerge, + EpLock: p.EpLock && other.EpLock, + EpErase: p.EpErase && other.EpErase, + Report: p.Report && other.Report, + ManageApp: p.ManageApp && other.ManageApp, + AppErase: p.AppErase && other.AppErase, + } +} diff --git a/internal/auth/mysql_compat.go b/internal/auth/mysql_compat.go index 2e18235c4..5c04dde10 100644 --- a/internal/auth/mysql_compat.go +++ b/internal/auth/mysql_compat.go @@ -16,7 +16,8 @@ package auth import ( "github.com/trim21/errgo" - "github.com/trim21/go-phpserialize" + + "github.com/bangumi/server/internal/pkg/serialize" ) func parseBool(s string) bool { @@ -24,46 +25,46 @@ func parseBool(s string) bool { } type phpPermission struct { - UserList string `php:"user_list"` - ManageUserGroup string `php:"manage_user_group"` - ManageUserPhoto string `php:"manage_user_photo"` - ManageTopicState string `php:"manage_topic_state"` - ManageReport string `php:"manage_report"` - UserBan string `php:"user_ban"` - ManageUser string `php:"manage_user"` - UserGroup string `php:"user_group"` - UserWikiApprove string `php:"user_wiki_approve"` - DoujinSubjectErase string `php:"doujin_subject_erase"` - UserWikiApply string `php:"user_wiki_apply"` - DoujinSubjectLock string `php:"doujin_subject_lock"` - SubjectEdit string `php:"subject_edit"` - SubjectLock string `php:"subject_lock"` - SubjectRefresh string `php:"subject_refresh"` - SubjectRelated string `php:"subject_related"` - SubjectMerge string `php:"subject_merge"` - SubjectErase string `php:"subject_erase"` - SubjectCoverLock string `php:"subject_cover_lock"` - SubjectCoverErase string `php:"subject_cover_erase"` - MonoEdit string `php:"mono_edit"` - MonoLock string `php:"mono_lock"` - MonoMerge string `php:"mono_merge"` - MonoErase string `php:"mono_erase"` - EpEdit string `php:"ep_edit"` - EpMove string `php:"ep_move"` - EpMerge string `php:"ep_merge"` - EpLock string `php:"ep_lock"` - EpErase string `php:"ep_erase"` - Report string `php:"report"` - ManageApp string `php:"manage_app"` - AppErase string `php:"app_erase"` + UserList string `php:"user_list" json:"user_list"` + ManageUserGroup string `php:"manage_user_group" json:"manage_user_group"` + ManageUserPhoto string `php:"manage_user_photo" json:"manage_user_photo"` + ManageTopicState string `php:"manage_topic_state" json:"manage_topic_state"` + ManageReport string `php:"manage_report" json:"manage_report"` + UserBan string `php:"user_ban" json:"user_ban"` + ManageUser string `php:"manage_user" json:"manage_user"` + UserGroup string `php:"user_group" json:"user_group"` + UserWikiApprove string `php:"user_wiki_approve" json:"user_wiki_approve"` + DoujinSubjectErase string `php:"doujin_subject_erase" json:"doujin_subject_erase"` + UserWikiApply string `php:"user_wiki_apply" json:"user_wiki_apply"` + DoujinSubjectLock string `php:"doujin_subject_lock" json:"doujin_subject_lock"` + SubjectEdit string `php:"subject_edit" json:"subject_edit"` + SubjectLock string `php:"subject_lock" json:"subject_lock"` + SubjectRefresh string `php:"subject_refresh" json:"subject_refresh"` + SubjectRelated string `php:"subject_related" json:"subject_related"` + SubjectMerge string `php:"subject_merge" json:"subject_merge"` + SubjectErase string `php:"subject_erase" json:"subject_erase"` + SubjectCoverLock string `php:"subject_cover_lock" json:"subject_cover_lock"` + SubjectCoverErase string `php:"subject_cover_erase" json:"subject_cover_erase"` + MonoEdit string `php:"mono_edit" json:"mono_edit"` + MonoLock string `php:"mono_lock" json:"mono_lock"` + MonoMerge string `php:"mono_merge" json:"mono_merge"` + MonoErase string `php:"mono_erase" json:"mono_erase"` + EpEdit string `php:"ep_edit" json:"ep_edit"` + EpMove string `php:"ep_move" json:"ep_move"` + EpMerge string `php:"ep_merge" json:"ep_merge"` + EpLock string `php:"ep_lock" json:"ep_lock"` + EpErase string `php:"ep_erase" json:"ep_erase"` + Report string `php:"report" json:"report"` + ManageApp string `php:"manage_app" json:"manage_app"` + AppErase string `php:"app_erase" json:"app_erase"` } -func parsePhpSerializedPermission(b []byte) (Permission, error) { +func parseSerializedPermission(b []byte) (Permission, error) { var p phpPermission if len(b) > 0 { - err := phpserialize.Unmarshal(b, &p) + err := serialize.Decode(b, &p) if err != nil { - return Permission{}, errgo.Wrap(err, "parsing permission: phpserialize.Unmarshal") + return Permission{}, errgo.Wrap(err, "parsing permission: serialize.Decode") } } diff --git a/internal/auth/mysql_compat_internal_test.go b/internal/auth/mysql_compat_internal_test.go index 936bc0476..5d2ae834c 100644 --- a/internal/auth/mysql_compat_internal_test.go +++ b/internal/auth/mysql_compat_internal_test.go @@ -23,7 +23,7 @@ import ( func TestUnmarshalPHP(t *testing.T) { t.Parallel() raw := "a:2:{s:15:\"user_wiki_apply\";s:1:\"1\";s:6:\"report\";s:1:\"1\";}" - p, err := parsePhpSerializedPermission([]byte(raw)) + p, err := parseSerializedPermission([]byte(raw)) require.NoError(t, err) require.True(t, p.Report) require.False(t, p.ManageReport) diff --git a/internal/auth/mysql_repository.go b/internal/auth/mysql_repository.go index f477fde9f..9e5ede854 100644 --- a/internal/auth/mysql_repository.go +++ b/internal/auth/mysql_repository.go @@ -16,62 +16,47 @@ package auth import ( "context" - "encoding/json" + "database/sql" "errors" - "strconv" "time" + "github.com/jmoiron/sqlx" "github.com/trim21/errgo" "go.uber.org/zap" "gorm.io/gorm" - "github.com/bangumi/server/dal/dao" "github.com/bangumi/server/dal/query" "github.com/bangumi/server/domain/gerr" - "github.com/bangumi/server/internal/model" "github.com/bangumi/server/internal/pkg/gstr" - "github.com/bangumi/server/internal/pkg/logger" - "github.com/bangumi/server/internal/pkg/random" + "github.com/bangumi/server/internal/user" ) -func NewMysqlRepo(q *query.Query, log *zap.Logger) Repo { - return mysqlRepo{q: q, log: log.Named("auth.mysqlRepo")} +func NewMysqlRepo(q *query.Query, log *zap.Logger, db *sqlx.DB) Repo { + return mysqlRepo{ + q: q, + log: log.Named("auth.mysqlRepo"), + db: db, + } } type mysqlRepo struct { q *query.Query + db *sqlx.DB log *zap.Logger } -func (m mysqlRepo) GetByEmail(ctx context.Context, email string) (UserInfo, []byte, error) { - u, err := m.q.Member.WithContext(ctx).Where(m.q.Member.Email.Eq(email)).Take() - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return UserInfo{}, nil, gerr.ErrNotFound - } - - m.log.Error("unexpected error happened", zap.Error(err)) - return UserInfo{}, nil, errgo.Wrap(err, "gorm") - } - - return UserInfo{ - RegTime: time.Unix(u.Regdate, 0), - ID: u.ID, - GroupID: u.Groupid, - }, u.PasswordCrypt, nil -} - func (m mysqlRepo) GetByToken(ctx context.Context, token string) (UserInfo, error) { - access, err := m.q.AccessToken.WithContext(ctx). - Where(m.q.AccessToken.AccessToken.Eq(token), m.q.AccessToken.ExpiredAt.Gte(time.Now())). - First() + var access struct { + UserID string `db:"user_id"` + } + err := m.db.GetContext(ctx, &access, + `select user_id from chii_oauth_access_tokens + where access_token = BINARY ? and expires > ? limit 1`, token, time.Now()) if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { + if errors.Is(err, sql.ErrNoRows) { return UserInfo{}, gerr.ErrNotFound } - m.log.Error("unexpected error happened", zap.Error(err)) - return UserInfo{}, errgo.Wrap(err, "gorm") } @@ -81,24 +66,32 @@ func (m mysqlRepo) GetByToken(ctx context.Context, token string) (UserInfo, erro return UserInfo{}, errgo.Wrap(err, "parsing user id") } - u, err := m.q.Member.WithContext(ctx).Where(m.q.Member.ID.Eq(id)).Take() - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - m.log.Error("can't find user of access token", - zap.String("token", token), zap.String("uid", access.UserID)) + var u struct { + Regdate int64 + GroupID user.GroupID + ACL string + } + err = m.db.QueryRowContext(ctx, `select regdate, groupid, acl from chii_members where uid = ? limit 1`, id). + Scan(&u.Regdate, &u.GroupID, &u.ACL) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { return UserInfo{}, gerr.ErrNotFound } - m.log.Error("unexpected error happened", zap.Error(err)) - return UserInfo{}, errgo.Wrap(err, "gorm") } + perm, err := parseSerializedPermission([]byte(u.ACL)) + if err != nil { + return UserInfo{}, errgo.Wrap(err, "parsing permission") + } + return UserInfo{ - RegTime: time.Unix(u.Regdate, 0), - ID: u.ID, - GroupID: u.Groupid, + RegTime: time.Unix(u.Regdate, 0), + ID: id, + GroupID: u.GroupID, + Permission: perm, }, nil } @@ -109,12 +102,10 @@ func (m mysqlRepo) GetPermission(ctx context.Context, groupID uint8) (Permission m.log.Error("can't find permission for group", zap.Uint8("user_group_id", groupID)) return Permission{}, nil } - - m.log.Error("unexpected error", zap.Error(err)) return Permission{}, errgo.Wrap(err, "dal") } - p, err := parsePhpSerializedPermission(r.Perm) + p, err := parseSerializedPermission(r.Perm) if err != nil { m.log.Error("failed to decode php serialized content", zap.Error(err), zap.Uint8("user_group_id", groupID)) return Permission{}, nil @@ -122,126 +113,3 @@ func (m mysqlRepo) GetPermission(ctx context.Context, groupID uint8) (Permission return p, nil } - -const defaultAccessTokenLength = 40 - -func (m mysqlRepo) CreateAccessToken( - ctx context.Context, id model.UserID, name string, expiration time.Duration, -) (string, error) { - token := random.Base62String(defaultAccessTokenLength) - var now = time.Now() - - var info = TokenInfo{ - Name: name, - CreatedAt: now, - } - - var expiredAt = now.Add(expiration) - if expiration < 0 { - expiredAt = time.Time{} - } - - infoByte, err := json.Marshal(info) - if err != nil { - // marshal simple struct should never fail - m.log.Fatal("marshal simple struct should never fail", - zap.Error(err), zap.String("name", name), zap.Time("now", now)) - panic("unexpected json encode error") - } - - err = m.q.AccessToken.WithContext(ctx).Create(&dao.AccessToken{ - Type: TokenTypeAccessToken, - AccessToken: token, - ClientID: "access token", - UserID: strconv.FormatUint(uint64(id), 10), - ExpiredAt: expiredAt, - Scope: nil, - Info: infoByte, - }) - if err != nil { - m.log.Error("unexpected error happened", zap.Error(err)) - return "", errgo.Wrap(err, "dal") - } - - return token, nil -} - -type TokenInfo struct { - CreatedAt time.Time `json:"created_at"` - Name string `json:"name"` -} - -func (m mysqlRepo) ListAccessToken(ctx context.Context, userID model.UserID) ([]AccessToken, error) { - records, err := m.q.AccessToken.WithContext(ctx). - Where(m.q.AccessToken.UserID.Eq(strconv.FormatUint(uint64(userID), 10)), - m.q.AccessToken.ExpiredAt.Gte(time.Now())).Find() - if err != nil { - m.log.Error("unexpected error happened", zap.Error(err)) - return nil, errgo.Wrap(err, "dal") - } - - var tokens = make([]AccessToken, len(records)) - for i, record := range records { - tokens[i] = convertAccessToken(record) - } - - return tokens, errgo.Wrap(err, "dal") -} - -const defaultOauthAccessExpiration = time.Hour * 168 - -func convertAccessToken(t *dao.AccessToken) AccessToken { - var createdAt time.Time - var name = "oauth token" - - switch t.Type { - case TokenTypeAccessToken: - if len(t.Info) > 0 { - var info TokenInfo - if err := json.Unmarshal(t.Info, &info); err != nil { - logger.Fatal("unexpected error when trying to unmarshal json data", - zap.Error(err), zap.ByteString("raw", t.Info)) - } - name = info.Name - createdAt = info.CreatedAt - } else { - name = "personal access token" - } - case TokenTypeOauthToken: - createdAt = t.ExpiredAt.Add(-defaultOauthAccessExpiration) - } - - v, err := strconv.ParseUint(t.UserID, 10, 32) - if err != nil { - logger.Fatal("parsing UserID", zap.String("raw", t.UserID), zap.Error(err)) - } - - return AccessToken{ - ExpiredAt: t.ExpiredAt, - CreatedAt: createdAt, - Name: name, - UserID: model.UserID(v), - ClientID: t.ClientID, - ID: t.ID, - } -} - -func (m mysqlRepo) DeleteAccessToken(ctx context.Context, id uint32) (bool, error) { - info, err := m.q.AccessToken.WithContext(ctx).Where(m.q.AccessToken.ID.Eq(id)).Delete() - - return info.RowsAffected > 0, errgo.Wrap(err, "dal.Delete") -} - -func (m mysqlRepo) GetTokenByID(ctx context.Context, id uint32) (AccessToken, error) { - record, err := m.q.AccessToken.WithContext(ctx).Where(m.q.AccessToken.ID.Eq(id)).Take() - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return AccessToken{}, gerr.ErrNotFound - } - - m.log.Error("unexpected error happened", zap.Error(err)) - return AccessToken{}, errgo.Wrap(err, "dal") - } - - return convertAccessToken(record), errgo.Wrap(err, "dal") -} diff --git a/internal/auth/mysql_repository_test.go b/internal/auth/mysql_repository_test.go index daa1f7a3a..f954e7b2f 100644 --- a/internal/auth/mysql_repository_test.go +++ b/internal/auth/mysql_repository_test.go @@ -16,34 +16,33 @@ package auth_test import ( "context" - "strconv" + "strings" "testing" - "time" + "github.com/jmoiron/sqlx" + "github.com/samber/lo" "github.com/stretchr/testify/require" "go.uber.org/zap" - "github.com/bangumi/server/dal/dao" "github.com/bangumi/server/dal/query" "github.com/bangumi/server/domain/gerr" "github.com/bangumi/server/internal/auth" - "github.com/bangumi/server/internal/pkg/gtime" "github.com/bangumi/server/internal/pkg/test" ) -func getRepo(t *testing.T) (auth.Repo, *query.Query) { +func getRepo(t *testing.T) auth.Repo { t.Helper() q := query.Use(test.GetGorm(t)) - repo := auth.NewMysqlRepo(q, zap.NewNop()) + repo := auth.NewMysqlRepo(q, zap.NewNop(), sqlx.NewDb(lo.Must(q.DB().DB()), "mysql")) - return repo, q + return repo } func TestMysqlRepo_GetByToken_NotFound(t *testing.T) { test.RequireEnv(t, "mysql") t.Parallel() - repo, _ := getRepo(t) + repo := getRepo(t) _, err := repo.GetByToken(context.Background(), "not exist token") require.ErrorIs(t, err, gerr.ErrNotFound) @@ -53,7 +52,7 @@ func TestMysqlRepo_GetByToken(t *testing.T) { test.RequireEnv(t, "mysql") t.Parallel() - repo, _ := getRepo(t) + repo := getRepo(t) u, err := repo.GetByToken(context.Background(), "a_development_access_token") require.NoError(t, err) @@ -61,86 +60,22 @@ func TestMysqlRepo_GetByToken(t *testing.T) { require.EqualValues(t, 382951, u.ID) } -func TestMysqlRepo_GetByToken_expired(t *testing.T) { +func TestMysqlRepo_GetByToken_case_sensitive(t *testing.T) { test.RequireEnv(t, "mysql") t.Parallel() - repo, _ := getRepo(t) + repo := getRepo(t) - _, err := repo.GetByToken(context.Background(), "a_expired_token") + _, err := repo.GetByToken(context.Background(), strings.ToUpper("a_development_access_token")) require.ErrorIs(t, err, gerr.ErrNotFound) } -func TestMysqlRepo_CreateAccessToken(t *testing.T) { - test.RequireEnv(t, "mysql") - t.Parallel() - - repo, q := getRepo(t) - t.Cleanup(func() { - _, err := q.AccessToken.WithContext(context.TODO()).Where(q.AccessToken.UserID.Eq("1")).Delete() - require.NoError(t, err) - }) - - token, err := repo.CreateAccessToken(context.Background(), 1, "token name", gtime.OneWeek) - require.NoError(t, err) - require.Len(t, token, 40) -} - -func TestMysqlRepo_DeleteAccessToken(t *testing.T) { +func TestMysqlRepo_GetByToken_expired(t *testing.T) { test.RequireEnv(t, "mysql") t.Parallel() - const id = 100 - repo, q := getRepo(t) - - cleanup := func() { - _, err := q.AccessToken.WithContext(context.TODO()).Where(q.AccessToken.ID.Eq(id)).Delete() - require.NoError(t, err) - } - t.Cleanup(cleanup) - - err := q.AccessToken.WithContext(context.Background()).Create(&dao.AccessToken{ - ID: id, - Type: auth.TokenTypeAccessToken, - AccessToken: t.Name(), - ClientID: "access token", - UserID: "2", - ExpiredAt: time.Now().Add(gtime.OneWeek), - Scope: nil, - Info: []byte{}, - }) - require.NoError(t, err) - - ok, err := repo.DeleteAccessToken(context.Background(), id) - require.NoError(t, err) - require.True(t, ok) -} - -func TestMysqlRepo_ListAccessToken(t *testing.T) { - test.RequireEnv(t, "mysql") - t.Parallel() + repo := getRepo(t) - repo, q := getRepo(t) - - test.RunAndCleanup(t, func() { - _, err := q.AccessToken.WithContext(context.TODO()).Where(q.AccessToken.UserID.Eq("3")).Delete() - require.NoError(t, err) - }) - - for i := 1; i < 5; i++ { - err := q.AccessToken.WithContext(context.Background()).Create(&dao.AccessToken{ - Type: auth.TokenTypeAccessToken, - AccessToken: t.Name() + strconv.Itoa(i), - ClientID: "access token", - UserID: "3", - ExpiredAt: time.Now().Add(gtime.OneWeek), - Scope: nil, - Info: []byte{}, - }) - require.NoError(t, err) - } - - tokens, err := repo.ListAccessToken(context.Background(), 3) - require.NoError(t, err) - require.Len(t, tokens, 4) + _, err := repo.GetByToken(context.Background(), "a_expired_token") + require.ErrorIs(t, err, gerr.ErrNotFound) } diff --git a/internal/auth/service.go b/internal/auth/service.go index c07eb3333..2382ef25a 100644 --- a/internal/auth/service.go +++ b/internal/auth/service.go @@ -15,19 +15,13 @@ package auth import ( - "context" - "crypto/md5" //nolint:gosec - "encoding/hex" - "errors" + "context" //nolint:gosec "time" "github.com/trim21/errgo" "go.uber.org/zap" - "golang.org/x/crypto/bcrypt" - "github.com/bangumi/server/domain/gerr" "github.com/bangumi/server/internal/auth/internal/cachekey" - "github.com/bangumi/server/internal/model" "github.com/bangumi/server/internal/pkg/cache" "github.com/bangumi/server/internal/user" ) @@ -53,77 +47,6 @@ type service struct { log *zap.Logger } -func (s service) GetByID(ctx context.Context, userID model.UserID) (Auth, error) { - var cacheKey = cachekey.User(userID) - - var a UserInfo - ok, err := s.cache.Get(ctx, cacheKey, &a) - if err != nil { - return Auth{}, errgo.Wrap(err, "cache.Get") - } - - if !ok { - var u user.User - u, err = s.user.GetByID(ctx, userID) - if err != nil { - return Auth{}, errgo.Wrap(err, "AuthRepo.GetByID") - } - - a = UserInfo{ - RegTime: u.RegistrationTime, - ID: u.ID, - GroupID: u.UserGroup, - } - - _ = s.cache.Set(ctx, cacheKey, a, time.Hour) - } - - permission, err := s.getPermission(ctx, a.GroupID) - if err != nil { - return Auth{}, err - } - - return Auth{ - Login: true, - RegTime: a.RegTime, - ID: a.ID, - GroupID: a.GroupID, - Permission: permission, - }, nil -} - -func (s service) Login(ctx context.Context, email, password string) (Auth, bool, error) { - var a, hashedPassword, err = s.repo.GetByEmail(ctx, email) - if err != nil { - if errors.Is(err, gerr.ErrNotFound) { - return Auth{}, false, nil - } - - return Auth{}, false, errgo.Wrap(err, "repo.GetByEmail") - } - - ok, err := s.ComparePassword(hashedPassword, password) - if err != nil { - s.log.Error("unexpected error when comparing password with bcrypt", zap.Error(err)) - return Auth{}, false, err - } - if !ok { - return Auth{}, false, nil - } - - p, err := s.getPermission(ctx, a.GroupID) - if err != nil { - return Auth{}, false, err - } - - return Auth{ - RegTime: a.RegTime, - ID: a.ID, - GroupID: a.GroupID, - Permission: p, - }, true, nil -} - func (s service) GetByToken(ctx context.Context, token string) (Auth, error) { var a UserInfo var cacheKey = cachekey.Auth(token) @@ -139,7 +62,7 @@ func (s service) GetByToken(ctx context.Context, token string) (Auth, error) { return Auth{}, errgo.Wrap(err, "AuthRepo.GetByID") } - _ = s.cache.Set(ctx, cacheKey, a, time.Hour) + _ = s.cache.Set(ctx, cacheKey, a, time.Minute*10) } permission, err := s.getPermission(ctx, a.GroupID) @@ -152,31 +75,10 @@ func (s service) GetByToken(ctx context.Context, token string) (Auth, error) { RegTime: a.RegTime, ID: a.ID, GroupID: a.GroupID, - Permission: permission, + Permission: permission.Merge(a.Permission), }, nil } -func (s service) ComparePassword(hashed []byte, password string) (bool, error) { - p := preProcessPassword(password) - - if err := bcrypt.CompareHashAndPassword(hashed, p); err != nil { - if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) { - return false, nil - } - - return false, errgo.Wrap(err, "bcrypt.CompareHashAndPassword") - } - - return true, nil -} - -func preProcessPassword(s string) []byte { - // don't know why old code base use md5 to hash password first - p := md5.Sum([]byte(s)) //nolint:gosec - - return []byte(hex.EncodeToString(p[:])) -} - func (s service) getPermission(ctx context.Context, id user.GroupID) (Permission, error) { p, ok := s.permCache.Get(ctx, id) @@ -193,25 +95,3 @@ func (s service) getPermission(ctx context.Context, id user.GroupID) (Permission return p, nil } - -func (s service) CreateAccessToken( - ctx context.Context, userID model.UserID, name string, expiration time.Duration, -) (string, error) { - token, err := s.repo.CreateAccessToken(ctx, userID, name, expiration) - return token, errgo.Wrap(err, "repo.CreateAccessToken") -} - -func (s service) ListAccessToken(ctx context.Context, userID model.UserID) ([]AccessToken, error) { - tokens, err := s.repo.ListAccessToken(ctx, userID) - return tokens, errgo.Wrap(err, "repo.ListAccessToken") -} - -func (s service) DeleteAccessToken(ctx context.Context, id uint32) (bool, error) { - result, err := s.repo.DeleteAccessToken(ctx, id) - return result, errgo.Wrap(err, "repo.DeleteAccessToken") -} - -func (s service) GetTokenByID(ctx context.Context, id uint32) (AccessToken, error) { - result, err := s.repo.GetTokenByID(ctx, id) - return result, errgo.Wrap(err, "repo.GetTokenByID") -} diff --git a/internal/auth/service_test.go b/internal/auth/service_test.go index 3ba320bfe..43fb20196 100644 --- a/internal/auth/service_test.go +++ b/internal/auth/service_test.go @@ -29,26 +29,12 @@ import ( "github.com/bangumi/server/internal/user" ) -func getService() auth.Service { - return auth.NewService(nil, nil, zap.NewNop(), cache.NewNoop()) -} - -func TestService_ComparePassword(t *testing.T) { - t.Parallel() - s := getService() - var hashed = []byte("$2a$12$GA5Pr9GhsyLJcSPoTpYBY.JqTzYZb2nfgSeZ1EK38bfgk/Rykkvuq") - var input = "lovemeplease" - - eq, err := s.ComparePassword(hashed, input) - require.NoError(t, err) - require.True(t, eq) -} - func TestService_GetByToken(t *testing.T) { t.Parallel() var m = mocks.NewAuthRepo(t) - m.EXPECT().GetByToken(mock.Anything, test.TreeHoleAccessToken).Return(auth.UserInfo{GroupID: 2}, nil) + m.EXPECT().GetByToken(mock.Anything, test.TreeHoleAccessToken). + Return(auth.UserInfo{GroupID: 2, Permission: auth.Permission{EpEdit: true}}, nil) m.EXPECT().GetPermission(mock.Anything, user.GroupID(2)).Return(auth.Permission{EpEdit: true}, nil) var u = mocks.NewUserRepo(t) diff --git a/internal/auth/user_group.go b/internal/auth/user_group.go index be8dd4c35..fd51a8108 100644 --- a/internal/auth/user_group.go +++ b/internal/auth/user_group.go @@ -15,15 +15,13 @@ package auth const ( - UserGroupAdmin uint8 = iota + 1 - UserGroupBangumiAdmin - UserGroupWindowAdmin - UserGroupQuite - UserGroupBanned - _ - _ - UserGroupCharacterAdmin - UserGroupWikiAdmin - UserGroupNormal - UserGroupWikiEditor + UserGroupAdmin uint8 = 1 + UserGroupBangumiAdmin uint8 = 2 + UserGroupWindowAdmin uint8 = 3 + UserGroupQuite uint8 = 4 + UserGroupBanned uint8 = 5 + UserGroupCharacterAdmin uint8 = 8 + UserGroupWikiAdmin uint8 = 9 + UserGroupNormal uint8 = 10 + UserGroupWikiEditor uint8 = 11 ) diff --git a/internal/cachekey/cachekey.go b/internal/cachekey/cachekey.go index 99ce587f6..524fe1ded 100644 --- a/internal/cachekey/cachekey.go +++ b/internal/cachekey/cachekey.go @@ -35,6 +35,14 @@ func Subject(id model.SubjectID) string { return resPrefix + "subject:" + strconv.FormatUint(uint64(id), 10) } +func SubjectBrowse(s string, limit, offset int) string { + return resPrefix + "subject::browse:" + s + ":" + strconv.Itoa(limit) + ":" + strconv.Itoa(offset) +} + +func SubjectBrowseCount(s string) string { + return resPrefix + "subject::browse:" + s + "::count" +} + func Episode(id model.EpisodeID) string { return resPrefix + "episode:" + strconv.FormatUint(uint64(id), 10) } @@ -46,3 +54,7 @@ func Index(id model.IndexID) string { func User(id model.UserID) string { return resPrefix + "user:" + strconv.FormatUint(uint64(id), 10) } + +func SubjectMetaTag(id model.SubjectID) string { + return "chii:v0:subject:meta-tags:" + strconv.FormatUint(uint64(id), 10) +} diff --git a/internal/character/mysql_repository.go b/internal/character/mysql_repository.go index 47bc3640c..a8abcaeed 100644 --- a/internal/character/mysql_repository.go +++ b/internal/character/mysql_repository.go @@ -47,7 +47,6 @@ func (r mysqlRepo) Get(ctx context.Context, id model.CharacterID) (model.Charact return model.Character{}, gerr.ErrCharacterNotFound } - r.log.Error("unexpected error happened", zap.Error(err)) return model.Character{}, errgo.Wrap(err, "dal") } @@ -60,7 +59,6 @@ func (r mysqlRepo) GetByIDs( records, err := r.q.Character.WithContext(ctx).Preload(r.q.Character.Fields). Where(r.q.Character.ID.In(slice.ToUint32(ids)...)).Find() if err != nil { - r.log.Error("unexpected error happened", zap.Error(err)) return nil, errgo.Wrap(err, "dal") } @@ -80,7 +78,6 @@ func (r mysqlRepo) GetPersonRelated( Order(r.q.Cast.SubjectID). Find() if err != nil { - r.log.Error("unexpected error happened", zap.Error(err)) return nil, errgo.Wrap(err, "dal") } @@ -103,7 +100,6 @@ func (r mysqlRepo) GetSubjectRelated( Where(r.q.CharacterSubjects.SubjectID.Eq(subjectID)). Order(r.q.CharacterSubjects.CharacterID).Find() if err != nil { - r.log.Error("unexpected error happened", zap.Error(err)) return nil, errgo.Wrap(err, "dal") } @@ -137,7 +133,6 @@ func (r mysqlRepo) GetSubjectRelationByIDs( Find() if err != nil { - r.log.Error("unexpected error happened", zap.Error(err)) return nil, errgo.Wrap(err, "dal") } @@ -156,12 +151,12 @@ func (r mysqlRepo) GetSubjectRelationByIDs( func convertDao(s *dao.Character) model.Character { return model.Character{ ID: s.ID, - Name: s.Name, + Name: string(s.Name), Type: s.Role, Image: s.Img, Summary: s.Summary, Locked: s.Ban != 0, - Infobox: s.Infobox, + Infobox: string(s.Infobox), CollectCount: s.Collects, CommentCount: s.Comment, NSFW: s.Nsfw, diff --git a/internal/character/mysql_repository_internal_test.go b/internal/character/mysql_repository_internal_test.go new file mode 100644 index 000000000..5e4a671ef --- /dev/null +++ b/internal/character/mysql_repository_internal_test.go @@ -0,0 +1,35 @@ +package character + +import ( + "html" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/bangumi/server/dal/dao" + "github.com/bangumi/server/dal/utiltype" +) + +func TestConvertDao_UnescapesHTMLStrings(t *testing.T) { + t.Parallel() + + wikiValue := `[nickname|"First Hassan"] Tom & Jerry 'quote'` + nameValue := `Tom & Jerry ` + + character := convertDao(&dao.Character{ + Name: mustScanHTMLString(t, html.EscapeString(nameValue)), + Infobox: mustScanHTMLString(t, html.EscapeString(wikiValue)), + }) + + require.Equal(t, nameValue, character.Name) + require.Equal(t, wikiValue, character.Infobox) +} + +func mustScanHTMLString(t *testing.T, value string) utiltype.HTMLEscapedString { + t.Helper() + + var result utiltype.HTMLEscapedString + require.NoError(t, (&result).Scan(value)) + + return result +} diff --git a/internal/collections/domain.go b/internal/collections/domain.go index 98d5c815a..315172987 100644 --- a/internal/collections/domain.go +++ b/internal/collections/domain.go @@ -24,7 +24,7 @@ import ( "github.com/bangumi/server/internal/pkg/null" ) -type Repo interface { +type Repo interface { //nolint:interfacebloat // WithQuery is used to replace repo's query to txn WithQuery(query *query.Query) Repo CountSubjectCollections( @@ -53,7 +53,13 @@ type Repo interface { ) (collection.UserSubjectEpisodesCollection, error) UpdateSubjectCollection( - ctx context.Context, userID model.UserID, subjectID model.SubjectID, + ctx context.Context, userID model.UserID, subject model.Subject, + at time.Time, ip string, + update func(ctx context.Context, s *collection.Subject) (*collection.Subject, error), + ) error + + UpdateOrCreateSubjectCollection( + ctx context.Context, userID model.UserID, subject model.Subject, at time.Time, ip string, update func(ctx context.Context, s *collection.Subject) (*collection.Subject, error), ) error @@ -64,6 +70,34 @@ type Repo interface { episodeIDs []model.EpisodeID, collection collection.EpisodeCollection, at time.Time, ) (collection.UserSubjectEpisodesCollection, error) + + GetPersonCollection( + ctx context.Context, userID model.UserID, + cat collection.PersonCollectCategory, targetID model.PersonID, + ) (collection.UserPersonCollection, error) + + AddPersonCollection( + ctx context.Context, userID model.UserID, + cat collection.PersonCollectCategory, targetID model.PersonID, + ) error + + RemovePersonCollection( + ctx context.Context, userID model.UserID, + cat collection.PersonCollectCategory, targetID model.PersonID, + ) error + + CountPersonCollections( + ctx context.Context, + userID model.UserID, + cat collection.PersonCollectCategory, + ) (int64, error) + + ListPersonCollection( + ctx context.Context, + userID model.UserID, + cat collection.PersonCollectCategory, + limit, offset int, + ) ([]collection.UserPersonCollection, error) } type Update struct { diff --git a/internal/collections/domain/collection/model.go b/internal/collections/domain/collection/model.go index 5792c5aac..746c65215 100644 --- a/internal/collections/domain/collection/model.go +++ b/internal/collections/domain/collection/model.go @@ -32,6 +32,7 @@ const ( ) type UserSubjectCollection struct { + ID uint64 UpdatedAt time.Time Comment string Tags []string @@ -45,8 +46,24 @@ type UserSubjectCollection struct { } type UserEpisodeCollection struct { - ID model.EpisodeID - Type EpisodeCollection + ID model.EpisodeID + Type EpisodeCollection + UpdatedAt int64 } type UserSubjectEpisodesCollection map[model.EpisodeID]UserEpisodeCollection + +type PersonCollectCategory string + +const ( + PersonCollectCategoryPerson PersonCollectCategory = "prsn" + PersonCollectCategoryCharacter PersonCollectCategory = "crt" +) + +type UserPersonCollection struct { + ID uint32 + Category string + TargetID model.PersonID + UserID model.UserID + CreatedAt time.Time +} diff --git a/internal/collections/domain/collection/subject.go b/internal/collections/domain/collection/subject.go index 120ed75b6..f196e6871 100644 --- a/internal/collections/domain/collection/subject.go +++ b/internal/collections/domain/collection/subject.go @@ -19,6 +19,7 @@ import ( "github.com/samber/lo" "github.com/trim21/errgo" + "golang.org/x/text/unicode/norm" "github.com/bangumi/server/domain/gerr" "github.com/bangumi/server/internal/model" @@ -67,6 +68,13 @@ func NewSubjectCollection( }, nil } +func NewEmptySubjectCollection(subject model.SubjectID, user model.UserID) *Subject { + return &Subject{ + subject: subject, + user: user, + } +} + type Subject struct { subject model.SubjectID user model.UserID @@ -111,7 +119,7 @@ func (s *Subject) UpdateComment(comment string) error { return gerr.ErrInvisibleChar } - s.comment = comment + s.comment = norm.NFC.String(comment) return nil } @@ -122,12 +130,16 @@ func (s *Subject) UpdateTags(tags []string) error { return nil } - slice.Map(tags, strings.TrimSpace) + tags = slice.Map(tags, strings.TrimSpace) - if !lo.ContainsBy(tags, dam.AllPrintableChar) { + if lo.ContainsBy(tags, func(item string) bool { return !dam.AllPrintableChar(item) }) { return gerr.ErrInvisibleChar } + for i, tag := range tags { + tags[i] = norm.NFKC.String(tag) + } + s.tags = lo.Uniq(tags) return nil @@ -138,11 +150,16 @@ func (s *Subject) UpdateType(r SubjectCollection) { } func (s *Subject) UpdateRate(r uint8) error { - if r > 11 { + if r > 10 { return errgo.Wrap(gerr.ErrInput, "rate overflow") } - s.rate = r + if s.typeID == SubjectCollectionWish { + s.rate = 0 + } else { + s.rate = r + } + return nil } diff --git a/internal/collections/domain/collection/type.go b/internal/collections/domain/collection/type.go index ca45c230f..111ef8ed5 100644 --- a/internal/collections/domain/collection/type.go +++ b/internal/collections/domain/collection/type.go @@ -16,6 +16,10 @@ package collection type SubjectCollection uint8 +func (s SubjectCollection) IsValid() bool { + return s > 0 && s <= 5 +} + const ( SubjectCollectionAll SubjectCollection = 0 // 全部 SubjectCollectionWish SubjectCollection = 1 // 想看 diff --git a/internal/collections/infra/mysql_repo.go b/internal/collections/infra/mysql_repo.go index cbe334c75..0dd46da3f 100644 --- a/internal/collections/infra/mysql_repo.go +++ b/internal/collections/infra/mysql_repo.go @@ -15,20 +15,22 @@ package infra import ( + "cmp" "context" "errors" "fmt" + "slices" "sort" "strings" "time" "github.com/samber/lo" "github.com/trim21/errgo" - "github.com/trim21/go-phpserialize" "go.uber.org/zap" "gorm.io/gen" "gorm.io/gen/field" "gorm.io/gorm" + "gorm.io/gorm/clause" "github.com/bangumi/server/dal/dao" "github.com/bangumi/server/dal/query" @@ -37,7 +39,10 @@ import ( "github.com/bangumi/server/internal/collections" "github.com/bangumi/server/internal/collections/domain/collection" "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/pkg/dam" + "github.com/bangumi/server/internal/pkg/gmap" "github.com/bangumi/server/internal/pkg/gstr" + "github.com/bangumi/server/internal/pkg/serialize" "github.com/bangumi/server/internal/subject" ) @@ -57,13 +62,20 @@ type mysqlRepo struct { func (r mysqlRepo) getSubjectCollection( ctx context.Context, user model.UserID, subject model.SubjectID, -) (*collection.Subject, error) { +) (*dao.SubjectCollection, error) { s, err := r.q.SubjectCollection.WithContext(ctx). - Where(r.q.SubjectCollection.UserID.Eq(user), r.q.SubjectCollection.SubjectID.Eq(subject)).Take() + Where( + r.q.SubjectCollection.UserID.Eq(user), + r.q.SubjectCollection.SubjectID.Eq(subject), + ).Take() if err != nil { return nil, gerr.WrapGormError(err) } + return s, nil +} + +func (r mysqlRepo) convertToSubjectCollection(s *dao.SubjectCollection) (*collection.Subject, error) { return collection.NewSubjectCollection( s.SubjectID, s.UserID, @@ -80,50 +92,323 @@ func (r mysqlRepo) getSubjectCollection( func (r mysqlRepo) UpdateSubjectCollection( ctx context.Context, userID model.UserID, - subjectID model.SubjectID, + subject model.Subject, at time.Time, ip string, update func(ctx context.Context, s *collection.Subject) (*collection.Subject, error), ) error { - s, err := r.getSubjectCollection(ctx, userID, subjectID) + s, err := r.getSubjectCollection(ctx, userID, subject.ID) if err != nil { if errors.Is(err, gerr.ErrNotFound) { return gerr.ErrSubjectNotCollected } return err } - original := *s - s, err = update(ctx, s) + return r.updateOrCreateSubjectCollection(ctx, userID, subject, at, ip, update, s) +} + +func (r mysqlRepo) UpdateOrCreateSubjectCollection( + ctx context.Context, + userID model.UserID, + subject model.Subject, + at time.Time, + ip string, + update func(ctx context.Context, s *collection.Subject) (*collection.Subject, error), +) error { + s, err := r.getSubjectCollection(ctx, userID, subject.ID) + if err != nil { + if !errors.Is(err, gerr.ErrNotFound) { + return err + } + s = nil + } + return r.updateOrCreateSubjectCollection(ctx, userID, subject, at, ip, update, s) +} + +//nolint:funlen +func (r mysqlRepo) updateOrCreateSubjectCollection( + ctx context.Context, + userID model.UserID, + subject model.Subject, + at time.Time, + ip string, + update func(ctx context.Context, s *collection.Subject) (*collection.Subject, error), + obj *dao.SubjectCollection, +) error { + created := obj == nil + if created { + obj = &dao.SubjectCollection{ + SubjectID: subject.ID, + SubjectType: subject.TypeID, + UserID: userID, + Type: uint8(collection.SubjectCollectionDoing), + } + } + collectionSubject, err := r.convertToSubjectCollection(obj) + if err != nil { + return errgo.Trace(err) + } + + original := *collectionSubject + + relatedTags := slices.Clone(original.Tags()) + slices.Sort(relatedTags) + + // Update subject collection + s, err := update(ctx, collectionSubject) if err != nil { return errgo.Trace(err) } - t := r.q.SubjectCollection - var updater = []field.AssignExpr{ - t.UpdatedTime.Value(uint32(at.Unix())), t.Comment.Value(utiltype.HTMLEscapedString(s.Comment())), - t.HasComment.Value(s.Comment() != ""), t.Tag.Value(strings.Join(s.Tags(), " ")), - t.EpStatus.Value(s.Eps()), t.VolStatus.Value(s.Vols()), t.Rate.Value(s.Rate()), - t.Private.Value(uint8(s.Privacy())), t.Type.Value(uint8(s.TypeID())), + originalCollection := *obj + + if err = r.updateSubjectCollection(obj, &original, s, at, ip, created); err != nil { + return errgo.Trace(err) } - if s.TypeID() != original.TypeID() { - u, e := r.subjectCollectionUpdater(s.TypeID(), at) - if e != nil { - return errgo.Trace(e) + newTags := slices.Clone(s.Tags()) + slices.Sort(newTags) + + err = r.q.Transaction(func(tx *query.Query) error { + sanitizedTags, txErr := r.updateUserTags(ctx, tx, userID, subject, at, s) + if txErr != nil { + return errgo.Trace(txErr) } - updater = append(updater, u) + + sort.Strings(sanitizedTags) + + obj.Tag = strings.Join(sanitizedTags, " ") + + return errgo.Trace(r.q.SubjectCollection.WithContext(ctx).Clauses(clause.OnConflict{UpdateAll: true}).Create(obj)) + }) + if err != nil { + return err } - if ip != "" { - updater = append(updater, t.LastUpdateIP.Value(ip)) + if slices.Equal(relatedTags, newTags) { + relatedTags = nil + } else { + relatedTags = lo.Uniq(append(relatedTags, newTags...)) + } + + if len(relatedTags) != 0 { + err = r.q.Transaction(func(tx *query.Query) error { + return r.reCountSubjectTags(ctx, tx, subject, relatedTags) + }) + if err != nil { + return errgo.Trace(err) + } + } + + r.updateSubject(ctx, subject.ID) + + if obj.Rate != originalCollection.Rate { + if err := r.reCountSubjectRate(ctx, subject.ID, originalCollection.Rate, obj.Rate); err != nil { + r.log.Error("failed to update collection counts", zap.Error(err), zap.Uint32("subject_id", subject.ID)) + } + } + + return nil +} + +//nolint:funlen +func (r mysqlRepo) updateUserTags(ctx context.Context, + q *query.Query, + userID model.UserID, subject model.Subject, + at time.Time, s *collection.Subject) ([]string, error) { + tx := q.WithContext(ctx) + + if (len(s.Tags())) == 0 { + _, err := tx.TagList.Where(q.TagList.UID.Eq(userID), + q.TagList.Mid.Eq(subject.ID), + q.TagList.Cat.Eq(model.TagCatSubject)).Delete() + return nil, errgo.Trace(err) } - _, err = t.WithContext(ctx).Where(t.SubjectID.Eq(subjectID), t.UserID.Eq(userID)).UpdateSimple(updater...) + tags, err := tx.TagIndex.Select(). + Where(q.TagIndex.Name.In(s.Tags()...), q.TagIndex.Cat.Eq(model.TagCatSubject), + q.TagIndex.Type.Eq(subject.TypeID)).Find() + if err != nil { + return nil, errgo.Trace(err) + } + + var existsTags = lo.SliceToMap(tags, func(item *dao.TagIndex) (string, bool) { + return strings.ToLower(item.Name), true + }) + + var missingTags []string + for _, tag := range s.Tags() { + if !existsTags[strings.ToLower(tag)] { + missingTags = append(missingTags, tag) + } + } + + if len(missingTags) > 0 { + r.log.Info("create missing tags", zap.Strings("missing_tags", missingTags)) + err = tx.TagIndex.Create(lo.Map(missingTags, func(item string, index int) *dao.TagIndex { + return &dao.TagIndex{ + Name: item, + Cat: model.TagCatSubject, + Type: subject.TypeID, + Results: 1, + CreatedTime: uint32(at.Unix()), + UpdatedTime: uint32(at.Unix()), + } + })...) + + if err != nil { + return nil, errgo.Trace(err) + } + } + + tags, err = tx.TagIndex.Select(). + Where(q.TagIndex.Name.In(s.Tags()...), q.TagIndex.Cat.Eq(model.TagCatSubject), + q.TagIndex.Type.Eq(subject.TypeID)).Find() + if err != nil { + return nil, errgo.Trace(err) + } + + err = tx.TagList.Clauses(clause.OnConflict{DoNothing: true}). + Create(lo.Map(tags, func(item *dao.TagIndex, index int) *dao.TagList { + return &dao.TagList{ + Tid: item.ID, + UID: s.User(), + Cat: model.TagCatSubject, + Type: subject.TypeID, + Mid: subject.ID, + CreatedTime: uint32(at.Unix()), + } + })...) + if err != nil { + return nil, errgo.Trace(err) + } + + return lo.Map(tags, func(item *dao.TagIndex, index int) string { + return item.Name + }), nil +} + +//nolint:funlen +func (r mysqlRepo) reCountSubjectTags(ctx context.Context, tx *query.Query, + s model.Subject, relatedTags []string) error { + tagIndexs, err := tx.WithContext(ctx).TagIndex.Select(). + Where( + tx.TagIndex.Cat.Eq(model.TagCatSubject), + tx.TagIndex.Name.In(relatedTags...), + tx.TagIndex.Type.Eq(s.TypeID), + ).Order(tx.TagIndex.ID.Asc()).Find() + if err != nil { + return err + } + + // tag in (...) 操作不区分大小写,使用第一次出现的 tag + // 比如 {name="Galgame"} {name="galgame"} 在过滤后会只保留 {name="Galgame"} + seen := make(map[string]bool) + tagIndexs = lo.Filter(tagIndexs, func(item *dao.TagIndex, index int) bool { + n := strings.ToLower(item.Name) + if seen[n] { + return false + } + seen[n] = true + return true + }) + + db := tx.DB().WithContext(ctx) + + err = db.Exec(` + update chii_tag_neue_index as ti + set tag_results = ( + select count(1) + from chii_tag_neue_list as tl + where tl.tlt_cat = ti.tag_cat AND tl.tlt_tid = ti.tag_id and tl.tlt_type = ti.tag_type + ) + where ti.tag_cat = ? AND ti.tag_type = ? AND ti.tag_id IN ? + `, model.TagCatSubject, s.TypeID, + lo.Uniq(lo.Map(tagIndexs, func(item *dao.TagIndex, index int) uint32 { + return item.ID + })), + ).Error if err != nil { return errgo.Trace(err) } - r.updateSubject(ctx, subjectID) + tagList, err := tx.WithContext(ctx).TagList.Preload(tx.TagList.Tag). + Where(tx.TagList.Cat.Eq(model.TagCatSubject), tx.TagList.Mid.Eq(s.ID)).Find() + if err != nil { + return errgo.Trace(err) + } + + var count = make(map[string]uint) + var countMap = make(map[string]uint) + + for _, tag := range tagList { + if !dam.ValidateTag(tag.Tag.Name) { + continue + } + + count[tag.Tag.Name]++ + countMap[tag.Tag.Name] = uint(tag.Tag.Results) + } + + var phpTags = make([]subject.Tag, 0, len(count)) + + for name, c := range count { + phpTags = append(phpTags, subject.Tag{ + Name: lo.ToPtr(name), + Count: c, + TotalCount: countMap[name], + }) + } + + slices.SortFunc(phpTags, func(a, b subject.Tag) int { + return -cmp.Compare(a.Count, b.Count) + }) + + newTag, err := serialize.Encode(lo.Slice(phpTags, 0, 30)) //nolint:mnd + if err != nil { + return errgo.Wrap(err, "php.Marshal") + } + + _, err = tx.WithContext(ctx).SubjectField.Where(r.q.SubjectField.Sid.Eq(s.ID)). + UpdateSimple(r.q.SubjectField.Tags.Value(newTag)) + + return errgo.Wrap(err, "failed to update subject field") +} + +// 根据新旧 collection.Subject 状态 +// 更新 dao.SubjectCollection. +func (r mysqlRepo) updateSubjectCollection( + obj *dao.SubjectCollection, + original *collection.Subject, + s *collection.Subject, + at time.Time, + ip string, + isNew bool, +) error { + obj.UpdatedTime = uint32(at.Unix()) + obj.Comment = utiltype.HTMLEscapedString(s.Comment()) + obj.HasComment = s.Comment() != "" + obj.Tag = strings.Join(s.Tags(), " ") + obj.EpStatus = s.Eps() + obj.VolStatus = s.Vols() + obj.Rate = s.Rate() + obj.Private = uint8(s.Privacy()) + obj.Type = uint8(s.TypeID()) + + if isNew || s.TypeID() != original.TypeID() { + err := r.updateCollectionTime(obj, s.TypeID(), at) + if err != nil { + return errgo.Trace(err) + } + } + + // Update IP + if ip != "" { + obj.LastUpdateIP = ip + if isNew { + obj.CreateIP = ip + } + } return nil } @@ -139,7 +424,9 @@ func (r mysqlRepo) CountSubjectCollections( showPrivate bool, ) (int64, error) { q := r.q.SubjectCollection.WithContext(ctx). - Where(r.q.SubjectCollection.UserID.Eq(userID)) + Where(r.q.SubjectCollection.UserID.Eq(userID), + r.q.SubjectCollection.Type.Neq(0), + ) if subjectType != model.SubjectTypeAll { q = q.Where(r.q.SubjectCollection.SubjectType.Eq(subjectType)) @@ -171,7 +458,10 @@ func (r mysqlRepo) ListSubjectCollection( ) ([]collection.UserSubjectCollection, error) { q := r.q.SubjectCollection.WithContext(ctx). Order(r.q.SubjectCollection.UpdatedTime.Desc()). - Where(r.q.SubjectCollection.UserID.Eq(userID)).Limit(limit).Offset(offset) + Where( + r.q.SubjectCollection.UserID.Eq(userID), + r.q.SubjectCollection.Type.Neq(0), + ).Limit(limit).Offset(offset) if subjectType != model.SubjectTypeAll { q = q.Where(r.q.SubjectCollection.SubjectType.Eq(subjectType)) @@ -187,7 +477,6 @@ func (r mysqlRepo) ListSubjectCollection( collections, err := q.Find() if err != nil { - r.log.Error("unexpected error happened", zap.Error(err)) return nil, errgo.Wrap(err, "dal") } @@ -214,7 +503,11 @@ func (r mysqlRepo) GetSubjectCollection( ctx context.Context, userID model.UserID, subjectID model.SubjectID, ) (collection.UserSubjectCollection, error) { c, err := r.q.SubjectCollection.WithContext(ctx). - Where(r.q.SubjectCollection.UserID.Eq(userID), r.q.SubjectCollection.SubjectID.Eq(subjectID)).Take() + Where( + r.q.SubjectCollection.UserID.Eq(userID), + r.q.SubjectCollection.SubjectID.Eq(subjectID), + r.q.SubjectCollection.Type.Neq(0), + ).Take() if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return collection.UserSubjectCollection{}, gerr.ErrSubjectNotCollected @@ -224,6 +517,7 @@ func (r mysqlRepo) GetSubjectCollection( } return collection.UserSubjectCollection{ + ID: uint64(c.ID), UpdatedAt: time.Unix(int64(c.UpdatedTime), 0), Comment: string(c.Comment), Tags: gstr.Split(c.Tag, " "), @@ -253,7 +547,7 @@ func (r mysqlRepo) GetSubjectEpisodesCollection( return nil, errgo.Wrap(err, "query.EpCollection.Find") } - e, err := deserializePhpEpStatus(d.Status) + e, err := deserializeEpStatus(d.Status) if err != nil { return nil, err } @@ -262,12 +556,8 @@ func (r mysqlRepo) GetSubjectEpisodesCollection( } func (r mysqlRepo) updateSubject(ctx context.Context, subjectID model.SubjectID) { - if err := r.updateSubjectTags(ctx, subjectID); err != nil { - r.log.Error("failed to update subject tags", zap.Error(err)) - } - if err := r.reCountSubjectCollection(ctx, subjectID); err != nil { - r.log.Error("failed to update collection counts", zap.Error(err)) + r.log.Error("failed to update collection counts", zap.Error(err), zap.Uint32("subject_id", subjectID)) } } @@ -277,109 +567,267 @@ func (r mysqlRepo) reCountSubjectCollection(ctx context.Context, subjectID model Total uint32 `gorm:"total"` } - err := r.q.SubjectCollection.WithContext(ctx). - Select(r.q.SubjectCollection.Type.As("type"), r.q.SubjectCollection.Type.Count().As("total")). - Group(r.q.SubjectCollection.Type). - Where(r.q.SubjectCollection.SubjectID.Eq(subjectID)).Group(r.q.SubjectCollection.Type).Scan(&counts) - if err != nil { - return errgo.Wrap(err, "dal") - } + return r.q.Transaction(func(tx *query.Query) error { + err := tx.DB().WithContext(ctx).Raw(` + select interest_type as type, count(interest_type) as total + from chii_subject_interests + where interest_subject_id = ? and interest_type != 0 + group by interest_type + `, subjectID).Scan(&counts).Error + if err != nil { + return errgo.Wrap(err, "dal") + } - var updater = make([]field.AssignExpr, 0, 5) + var updater = make([]field.AssignExpr, 0, 5) + for _, count := range counts { + switch collection.SubjectCollection(count.Type) { + case collection.SubjectCollectionAll: + continue - for _, count := range counts { - switch collection.SubjectCollection(count.Type) { //nolint:exhaustive - case collection.SubjectCollectionDropped: - updater = append(updater, r.q.Subject.Dropped.Value(count.Total)) + case collection.SubjectCollectionDropped: + updater = append(updater, r.q.Subject.Dropped.Value(count.Total)) - case collection.SubjectCollectionWish: - updater = append(updater, r.q.Subject.Wish.Value(count.Total)) + case collection.SubjectCollectionWish: + updater = append(updater, r.q.Subject.Wish.Value(count.Total)) - case collection.SubjectCollectionDoing: - updater = append(updater, r.q.Subject.Doing.Value(count.Total)) + case collection.SubjectCollectionDoing: + updater = append(updater, r.q.Subject.Doing.Value(count.Total)) - case collection.SubjectCollectionOnHold: - updater = append(updater, r.q.Subject.OnHold.Value(count.Total)) + case collection.SubjectCollectionOnHold: + updater = append(updater, r.q.Subject.OnHold.Value(count.Total)) - case collection.SubjectCollectionDone: - updater = append(updater, r.q.Subject.Done.Value(count.Total)) + case collection.SubjectCollectionDone: + updater = append(updater, r.q.Subject.Done.Value(count.Total)) + } } - } - _, err = r.q.Subject.WithContext(ctx).Where(r.q.Subject.ID.Eq(subjectID)).UpdateSimple(updater...) - if err != nil { - return errgo.Wrap(err, "dal") - } + _, err = tx.Subject.WithContext(ctx).Where(r.q.Subject.ID.Eq(subjectID)).UpdateSimple(updater...) + if err != nil { + return errgo.Wrap(err, "dal") + } - return nil + return nil + }) } -func (r mysqlRepo) updateSubjectTags(ctx context.Context, subjectID model.SubjectID) error { - collections, err := r.q.SubjectCollection.WithContext(ctx). - Where( - r.q.SubjectCollection.SubjectID.Eq(subjectID), - r.q.SubjectCollection.Private.Neq(uint8(collection.CollectPrivacyBan)), - ).Find() - if err != nil { - return errgo.Wrap(err, "failed to get all collection") - } +//nolint:mnd,gocyclo +func (r mysqlRepo) reCountSubjectRate(ctx context.Context, subjectID model.SubjectID, before uint8, after uint8) error { + return r.q.Transaction(func(tx *query.Query) error { + var counts = make(map[uint8]uint32, 2) + + for _, rate := range []uint8{before, after} { + var count uint32 + if rate != 0 { + err := tx.DB().WithContext(ctx).Raw(` + select count(*) from chii_subject_interests + where interest_subject_id = ? and interest_private = 0 and interest_rate = ? + `, subjectID, rate).Scan(&count).Error + if err != nil { + return errgo.Wrap(err, "dal") + } + + counts[rate] = count + } + } - var tags = make(map[string]int) - for _, collection := range collections { - for _, s := range strings.Split(collection.Tag, " ") { - if s == "" { + var updater = make([]field.AssignExpr, 0, 2) + for rate, total := range counts { + switch rate { + case 0: continue + case 1: + updater = append(updater, tx.SubjectField.Rate1.Value(total)) + case 2: + updater = append(updater, tx.SubjectField.Rate2.Value(total)) + case 3: + updater = append(updater, tx.SubjectField.Rate3.Value(total)) + case 4: + updater = append(updater, tx.SubjectField.Rate4.Value(total)) + case 5: + updater = append(updater, tx.SubjectField.Rate5.Value(total)) + case 6: + updater = append(updater, tx.SubjectField.Rate6.Value(total)) + case 7: + updater = append(updater, tx.SubjectField.Rate7.Value(total)) + case 8: + updater = append(updater, tx.SubjectField.Rate8.Value(total)) + case 9: + updater = append(updater, tx.SubjectField.Rate9.Value(total)) + case 10: + updater = append(updater, tx.SubjectField.Rate10.Value(total)) } - tags[s]++ } - } - var phpTags = make([]subject.Tag, 0, len(tags)) + _, err := tx.SubjectField.WithContext(ctx).Where(r.q.SubjectField.Sid.Eq(subjectID)).UpdateSimple(updater...) + if err != nil { + return errgo.Wrap(err, "dal") + } - for name, count := range tags { - name := name - phpTags = append(phpTags, subject.Tag{ - Name: &name, - Count: count, - }) + return nil + }) +} + +func (r mysqlRepo) updateCollectionTime(obj *dao.SubjectCollection, + t collection.SubjectCollection, at time.Time) error { + switch t { + case collection.SubjectCollectionAll: + return errgo.Wrap(gerr.ErrInput, "can't set collection type to SubjectCollectionAll") + case collection.SubjectCollectionWish: + obj.WishTime = uint32(at.Unix()) + case collection.SubjectCollectionDone: + obj.DoneTime = uint32(at.Unix()) + case collection.SubjectCollectionDoing: + obj.DoingTime = uint32(at.Unix()) + case collection.SubjectCollectionDropped: + obj.DroppedTime = uint32(at.Unix()) + case collection.SubjectCollectionOnHold: + obj.OnHoldTime = uint32(at.Unix()) + default: + return errgo.Wrap(gerr.ErrInput, fmt.Sprintln("invalid subject collection type", t)) } + return nil +} - sort.Slice(phpTags, func(i, j int) bool { - if phpTags[i].Count != phpTags[j].Count { - return phpTags[i].Count > phpTags[j].Count +func (r mysqlRepo) GetPersonCollection( + ctx context.Context, userID model.UserID, + cat collection.PersonCollectCategory, targetID model.PersonID, +) (collection.UserPersonCollection, error) { + c, err := r.q.PersonCollect.WithContext(ctx). + Where(r.q.PersonCollect.UserID.Eq(userID), r.q.PersonCollect.Category.Eq(string(cat)), + r.q.PersonCollect.TargetID.Eq(targetID)).Take() + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return collection.UserPersonCollection{}, gerr.ErrNotFound } + return collection.UserPersonCollection{}, errgo.Wrap(err, "dal") + } + + return collection.UserPersonCollection{ + ID: c.ID, + Category: c.Category, + TargetID: c.TargetID, + UserID: c.UserID, + CreatedAt: time.Unix(int64(c.CreatedTime), 0), + }, nil +} - return *phpTags[i].Name > *phpTags[j].Name +func (r mysqlRepo) AddPersonCollection( + ctx context.Context, userID model.UserID, + cat collection.PersonCollectCategory, targetID model.PersonID, +) error { + collect := &dao.PersonCollect{ + UserID: userID, + Category: string(cat), + TargetID: targetID, + CreatedTime: uint32(time.Now().Unix()), + } + err := r.q.Transaction(func(tx *query.Query) error { + switch cat { + case collection.PersonCollectCategoryCharacter: + if _, err := tx.Character.WithContext(ctx).Where( + tx.Character.ID.Eq(targetID)).UpdateSimple(tx.Character.Collects.Add(1)); err != nil { + r.log.Error("failed to update character collects", zap.Error(err)) + return err + } + case collection.PersonCollectCategoryPerson: + if _, err := tx.Person.WithContext(ctx).Where( + tx.Person.ID.Eq(targetID)).UpdateSimple(tx.Person.Collects.Add(1)); err != nil { + r.log.Error("failed to update person collects", zap.Error(err)) + return err + } + } + if err := tx.PersonCollect.WithContext(ctx).Create(collect); err != nil { + r.log.Error("failed to create person collection record", zap.Error(err)) + return err + } + return nil }) + if err != nil { + return errgo.Wrap(err, "dal") + } + return nil +} - newTag, err := phpserialize.Marshal(lo.Slice(phpTags, 0, 30)) //nolint:gomnd +func (r mysqlRepo) RemovePersonCollection( + ctx context.Context, userID model.UserID, + cat collection.PersonCollectCategory, targetID model.PersonID, +) error { + err := r.q.Transaction(func(tx *query.Query) error { + switch cat { + case collection.PersonCollectCategoryCharacter: + if _, err := tx.Character.WithContext(ctx).Where( + tx.Character.ID.Eq(targetID)).UpdateSimple(tx.Character.Collects.Sub(1)); err != nil { + r.log.Error("failed to update character collects", zap.Error(err)) + return err + } + case collection.PersonCollectCategoryPerson: + if _, err := tx.Person.WithContext(ctx).Where( + tx.Person.ID.Eq(targetID)).UpdateSimple(tx.Person.Collects.Sub(1)); err != nil { + r.log.Error("failed to update person collects", zap.Error(err)) + return err + } + } + _, err := tx.PersonCollect.WithContext(ctx).Where( + tx.PersonCollect.UserID.Eq(userID), + tx.PersonCollect.Category.Eq(string(cat)), + tx.PersonCollect.TargetID.Eq(targetID), + ).Delete() + if err != nil { + r.log.Error("failed to delete person collection record", zap.Error(err)) + return err + } + return nil + }) if err != nil { - return errgo.Wrap(err, "php.Marshal") + return errgo.Wrap(err, "dal") } - _, err = r.q.SubjectField.WithContext(ctx).Where(r.q.SubjectField.Sid.Eq(subjectID)). - UpdateSimple(r.q.SubjectField.Tags.Value(newTag)) + return nil +} - return errgo.Wrap(err, "failed to update subject field") +func (r mysqlRepo) CountPersonCollections( + ctx context.Context, + userID model.UserID, + cat collection.PersonCollectCategory, +) (int64, error) { + q := r.q.PersonCollect.WithContext(ctx). + Where(r.q.PersonCollect.UserID.Eq(userID), r.q.PersonCollect.Category.Eq(string(cat))) + + c, err := q.Count() + if err != nil { + return 0, errgo.Wrap(err, "dal") + } + + return c, nil } -func (r mysqlRepo) subjectCollectionUpdater(t collection.SubjectCollection, at time.Time) (field.AssignExpr, error) { - switch t { - case collection.SubjectCollectionAll: - return nil, errgo.Wrap(gerr.ErrInput, "can't set collection type to SubjectCollectionAll") - case collection.SubjectCollectionWish: - return r.q.SubjectCollection.WishTime.Value(uint32(at.Unix())), nil - case collection.SubjectCollectionDone: - return r.q.SubjectCollection.DoneTime.Value(uint32(at.Unix())), nil - case collection.SubjectCollectionDoing: - return r.q.SubjectCollection.DoingTime.Value(uint32(at.Unix())), nil - case collection.SubjectCollectionDropped: - return r.q.SubjectCollection.DroppedTime.Value(uint32(at.Unix())), nil - case collection.SubjectCollectionOnHold: - return r.q.SubjectCollection.OnHoldTime.Value(uint32(at.Unix())), nil +func (r mysqlRepo) ListPersonCollection( + ctx context.Context, + userID model.UserID, + cat collection.PersonCollectCategory, + limit, offset int, +) ([]collection.UserPersonCollection, error) { + q := r.q.PersonCollect.WithContext(ctx). + Order(r.q.PersonCollect.CreatedTime.Desc()). + Where(r.q.PersonCollect.UserID.Eq(userID), r.q.PersonCollect.Category.Eq(string(cat))).Limit(limit).Offset(offset) + + collections, err := q.Find() + if err != nil { + return nil, errgo.Wrap(err, "dal") } - return nil, errgo.Wrap(gerr.ErrInput, fmt.Sprintln("invalid subject collection type", t)) + var results = make([]collection.UserPersonCollection, len(collections)) + for i, c := range collections { + results[i] = collection.UserPersonCollection{ + ID: c.ID, + Category: c.Category, + TargetID: c.TargetID, + UserID: c.UserID, + CreatedAt: time.Unix(int64(c.CreatedTime), 0), + } + } + + return results, nil } func (r mysqlRepo) UpdateEpisodeCollection( @@ -404,13 +852,13 @@ func (r mysqlRepo) UpdateEpisodeCollection( return nil, errgo.Wrap(err, "dal") } - e, err := deserializePhpEpStatus(d.Status) + e, err := deserializeEpStatus(d.Status) if err != nil { r.log.Error("failed to deserialize php-serialized bytes to go data", zap.Error(err)) return nil, err } - if updated := updateMysqlEpisodeCollection(e, episodeIDs, collectionType); !updated { + if updated := updateMysqlEpisodeCollection(e, episodeIDs, collectionType, at); !updated { return e.toModel(), nil } @@ -437,7 +885,7 @@ func (r mysqlRepo) createEpisodeCollection( at time.Time, ) (collection.UserSubjectEpisodesCollection, error) { var e = make(mysqlEpCollection, len(episodeIDs)) - updateMysqlEpisodeCollection(e, episodeIDs, collectionType) + updateMysqlEpisodeCollection(e, episodeIDs, collectionType, at) bytes, err := serializePhpEpStatus(e) if err != nil { @@ -445,7 +893,8 @@ func (r mysqlRepo) createEpisodeCollection( } table := r.q.EpCollection - err = table.WithContext(ctx).Where(table.UserID.Eq(userID), table.SubjectID.Eq(subjectID)).Create(&dao.EpCollection{ + err = table.WithContext(ctx).Clauses(clause.OnConflict{DoNothing: true}). + Where(table.UserID.Eq(userID), table.SubjectID.Eq(subjectID)).Create(&dao.EpCollection{ UserID: userID, SubjectID: subjectID, Status: bytes, @@ -463,6 +912,7 @@ func updateMysqlEpisodeCollection( e mysqlEpCollection, episodeIDs []model.EpisodeID, collectionType collection.EpisodeCollection, + now time.Time, ) bool { var updated bool @@ -477,16 +927,28 @@ func updateMysqlEpisodeCollection( delete(e, episodeID) updated = true } - } else { - for _, episodeID := range episodeIDs { - v, ok := e[episodeID] - if ok && v.Type == collectionType { + return updated + } + + for _, episodeID := range episodeIDs { + v, ok := e[episodeID] + if ok { + if v.Type == collectionType { continue } - e[episodeID] = mysqlEpCollectionItem{EpisodeID: episodeID, Type: collectionType} + v.Type = collectionType + v.UpdatedAt = gmap.SafeAssign(v.UpdatedAt, collectionType, now.Unix()) updated = true + continue + } + + e[episodeID] = mysqlEpCollectionItem{ + EpisodeID: episodeID, + Type: collectionType, + UpdatedAt: map[collection.EpisodeCollection]int64{collectionType: now.Unix()}, } + updated = true } return updated diff --git a/internal/collections/infra/mysql_repo_compat.go b/internal/collections/infra/mysql_repo_compat.go index bd95aba89..7de73afd8 100644 --- a/internal/collections/infra/mysql_repo_compat.go +++ b/internal/collections/infra/mysql_repo_compat.go @@ -17,31 +17,42 @@ package infra // handle php serialization import ( + "bytes" + "github.com/trim21/errgo" - "github.com/trim21/go-phpserialize" "github.com/bangumi/server/internal/collections/domain/collection" "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/pkg/serialize" ) type mysqlEpCollectionItem struct { - EpisodeID model.EpisodeID `php:"eid,string"` - Type collection.EpisodeCollection `php:"type"` + EpisodeID model.EpisodeID `php:"eid,string" json:"eid,string"` + Type collection.EpisodeCollection `php:"type" json:"type"` + // unix timestamp seconds + UpdatedAt map[collection.EpisodeCollection]int64 `php:"updated_at" json:"updated_at"` } +// nolint: gochecknoglobals +var emptyJSONArray = []byte("[]") + type mysqlEpCollection map[model.EpisodeID]mysqlEpCollectionItem -func deserializePhpEpStatus(phpSerialized []byte) (mysqlEpCollection, error) { +func deserializeEpStatus(serialized []byte) (mysqlEpCollection, error) { + if bytes.Equal(serialized, emptyJSONArray) { + serialized = []byte("{}") + } + var e map[model.EpisodeID]mysqlEpCollectionItem - if err := phpserialize.Unmarshal(phpSerialized, &e); err != nil { - return nil, errgo.Wrap(err, "php deserialize") + if err := serialize.Decode(serialized, &e); err != nil { + return nil, errgo.Wrap(err, "serialize.Decode:") } return e, nil } func serializePhpEpStatus(data mysqlEpCollection) ([]byte, error) { - b, err := phpserialize.Marshal(data) + b, err := serialize.Encode(data) return b, errgo.Wrap(err, "php serialize") } @@ -49,8 +60,9 @@ func (c mysqlEpCollection) toModel() collection.UserSubjectEpisodesCollection { var d = make(collection.UserSubjectEpisodesCollection, len(c)) for key, value := range c { d[key] = collection.UserEpisodeCollection{ - ID: value.EpisodeID, - Type: value.Type, + ID: value.EpisodeID, + Type: value.Type, + UpdatedAt: value.UpdatedAt[value.Type], } } diff --git a/internal/collections/infra/mysql_repo_test.go b/internal/collections/infra/mysql_repo_test.go index c52e97f65..7d8300845 100644 --- a/internal/collections/infra/mysql_repo_test.go +++ b/internal/collections/infra/mysql_repo_test.go @@ -19,10 +19,12 @@ import ( "testing" "time" + "github.com/samber/lo" "github.com/stretchr/testify/require" - "github.com/trim21/go-phpserialize" "go.uber.org/zap" "gorm.io/gen/field" + "gorm.io/gorm" + "gorm.io/gorm/clause" "github.com/bangumi/server/dal/dao" "github.com/bangumi/server/dal/query" @@ -30,12 +32,15 @@ import ( "github.com/bangumi/server/internal/collections/domain/collection" "github.com/bangumi/server/internal/collections/infra" "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/pkg/serialize" "github.com/bangumi/server/internal/pkg/test" + subject2 "github.com/bangumi/server/internal/subject" ) func getRepo(t *testing.T) (collections.Repo, *query.Query) { t.Helper() q := test.GetQuery(t) + repo, err := infra.NewMysqlRepo(q, zap.NewNop()) require.NoError(t, err) @@ -46,8 +51,8 @@ func TestMysqlRepo_GetCollection(t *testing.T) { t.Parallel() test.RequireEnv(t, test.EnvMysql) - const id model.UserID = 382951 - const subjectID model.SubjectID = 888998 + const id model.UserID = 30000 + const subjectID model.SubjectID = 10000 repo, q := getRepo(t) @@ -61,6 +66,7 @@ func TestMysqlRepo_GetCollection(t *testing.T) { UserID: id, SubjectID: subjectID, Rate: 2, + Type: 1, }) require.NoError(t, err) @@ -74,22 +80,28 @@ func TestMysqlRepo_CountSubjectCollections(t *testing.T) { t.Parallel() test.RequireEnv(t, test.EnvMysql) - const id model.UserID = 382952 + const id model.UserID = 31000 // parallel problem repo, q := getRepo(t) test.RunAndCleanup(t, func() { - _, err := q.SubjectCollection.WithContext(context.Background()).Where(q.SubjectCollection.UserID.Eq(id)).Delete() + _, err := q.SubjectCollection. + WithContext(context.Background()). + Where(q.SubjectCollection.UserID.Eq(id)). + Delete() require.NoError(t, err) }) for i := 0; i < 5; i++ { - err := q.SubjectCollection.WithContext(context.Background()).Create(&dao.SubjectCollection{ - UserID: id, - SubjectID: model.SubjectID(i + 100), - SubjectType: model.SubjectTypeAnime, - UpdatedTime: uint32(time.Now().Unix()), - }) + err := q.SubjectCollection. + WithContext(context.Background()). + Create(&dao.SubjectCollection{ + UserID: id, + Type: 2, + SubjectID: model.SubjectID(i + 100), + SubjectType: model.SubjectTypeAnime, + UpdatedTime: uint32(time.Now().Unix()), + }) require.NoError(t, err) } @@ -103,26 +115,84 @@ func TestMysqlRepo_ListSubjectCollection(t *testing.T) { t.Parallel() test.RequireEnv(t, test.EnvMysql) - const id model.UserID = 382951 + const uid model.UserID = 32000 - repo, _ := getRepo(t) + repo, q := getRepo(t) - data, err := repo.ListSubjectCollection(context.Background(), id, - model.SubjectTypeGame, collection.SubjectCollectionAll, true, 5, 0) + var err error + test.RunAndCleanup(t, func() { + _, err = q.SubjectCollection. + WithContext(context.Background()). + Where(q.SubjectCollection.UserID.Eq(uid)). + Delete() + require.NoError(t, err) + }) + + data, err := repo.ListSubjectCollection(context.Background(), uid, + model.SubjectTypeAll, collection.SubjectCollectionAll, true, 5, 0) require.NoError(t, err) - require.Len(t, data, 5) + require.Len(t, data, 0) + + for i := 0; i < 5; i++ { + err = q.SubjectCollection. + WithContext(context.Background()). + Create(&dao.SubjectCollection{ + UserID: uid, + Type: 2, + SubjectID: model.SubjectID(100 + i), + SubjectType: model.SubjectTypeAnime, + UpdatedTime: uint32(time.Now().Unix()), + }) + require.NoError(t, err) + } + + for i := uint32(0); i < 2; i++ { + err = q.SubjectCollection. + WithContext(context.Background()). + Create(&dao.SubjectCollection{ + UserID: uid, + Type: 2, + SubjectID: 200 + i, + SubjectType: model.SubjectTypeGame, + UpdatedTime: uint32(time.Now().Unix()), + }) + require.NoError(t, err) + } + + getList := func(subjectType model.SubjectType) []collection.UserSubjectCollection { + data, err = repo.ListSubjectCollection(context.Background(), uid, + subjectType, collection.SubjectCollectionAll, true, 10, 0) + require.NoError(t, err) + return data + } + require.Len(t, getList(model.SubjectTypeAll), 7) + require.Len(t, getList(model.SubjectTypeGame), 2) + require.Len(t, getList(model.SubjectTypeAnime), 5) + require.Len(t, getList(model.SubjectTypeBook), 0) } func TestMysqlRepo_GetEpisodeCollection(t *testing.T) { t.Parallel() test.RequireEnv(t, test.EnvMysql) - const userID model.UserID = 382951 - const subjectID model.SubjectID = 372487 + const uid model.UserID = 33000 + const sid model.SubjectID = 11000 + + repo, q := getRepo(t) + table := q.SubjectCollection + + test.RunAndCleanup(t, func() { + _, err := table.WithContext(context.TODO()).Where(field.Or(table.SubjectID.Eq(sid), table.UserID.Eq(uid))).Delete() + require.NoError(t, err) + }) + + now := time.Now() - repo, _ := getRepo(t) + _, err := repo.UpdateEpisodeCollection(context.Background(), + uid, sid, []model.EpisodeID{1, 2}, collection.EpisodeCollectionDone, now) + require.NoError(t, err) - ep, err := repo.GetSubjectEpisodesCollection(context.Background(), userID, subjectID) + ep, err := repo.GetSubjectEpisodesCollection(context.Background(), uid, sid) require.NoError(t, err) require.NotZero(t, len(ep)) @@ -134,12 +204,132 @@ func TestMysqlRepo_GetEpisodeCollection(t *testing.T) { } } +func TestMysqlRepo_UpdateOrCreateSubjectCollection(t *testing.T) { + test.RequireEnv(t, test.EnvMysql) + t.Parallel() + + const uid model.UserID = 34000 + const sid model.SubjectID = 12000 + const subjectType = model.SubjectTypeMusic + + subject := model.Subject{ID: sid, TypeID: subjectType} + + repo, q := getRepo(t) + table := q.SubjectCollection + + var r *dao.SubjectCollection + + err := q.Subject.WithContext(context.TODO()).Clauses(clause.OnConflict{DoNothing: true}). + Where(q.Subject.ID.Eq(sid)).Create(&dao.Subject{ID: sid}) + require.NoError(t, err) + + err = q.SubjectField.WithContext(context.TODO()).Clauses(clause.OnConflict{DoNothing: true}). + Where(q.Subject.ID.Eq(sid)).Create(&dao.SubjectField{Sid: sid, Tags: []byte("")}) + require.NoError(t, err) + + t.Cleanup(func() { + lo.Must(q.Subject.WithContext(context.TODO()).Where(q.Subject.ID.Eq(sid)).Delete()) + lo.Must(q.SubjectField.WithContext(context.TODO()).Where(q.SubjectField.Sid.Eq(sid)).Delete()) + }) + + test.RunAndCleanup(t, func() { + lo.Must(table.WithContext(context.TODO()).Where(field.Or(table.SubjectID.Eq(sid), table.UserID.Eq(uid))).Delete()) + }) + + err = table.WithContext(context.Background()).Create( + &dao.SubjectCollection{ + UserID: uid, SubjectID: sid + 1, Rate: 8, Type: uint8(collection.SubjectCollectionDoing), + }, + &dao.SubjectCollection{ + UserID: uid + 1, SubjectID: sid, Rate: 8, Type: uint8(collection.SubjectCollectionDoing), + }, + ) + require.NoError(t, err) + + now := time.Now() + + // DB 里没有数据 + _, err = table.WithContext(context.TODO()).Where(table.SubjectID.Eq(sid), table.UserID.Eq(uid)).Take() + require.Error(t, err) + + // 创建 + err = repo.UpdateOrCreateSubjectCollection(context.Background(), uid, subject, now, "", + func(ctx context.Context, s *collection.Subject) (*collection.Subject, error) { + return s, nil + }) + require.NoError(t, err) + + // DB 里有数据 + r, err = table.WithContext(context.TODO()).Where(table.SubjectID.Eq(sid), table.UserID.Eq(uid)).Take() + require.NoError(t, err) + require.EqualValues(t, now.Unix(), r.DoingTime) + + // 更新 + err = repo.UpdateOrCreateSubjectCollection(context.Background(), uid, subject, now, "", + func(ctx context.Context, s *collection.Subject) (*collection.Subject, error) { + s.UpdateType(collection.SubjectCollectionDropped) + require.NoError(t, s.UpdateComment("c")) + require.NoError(t, s.UpdateRate(1)) + require.NoError(t, s.UpdateTags([]string{"1", "2", "3"})) + return s, nil + }) + require.NoError(t, err) + + r, err = table.WithContext(context.TODO()).Where(table.SubjectID.Eq(sid), table.UserID.Eq(uid)).Take() + require.NoError(t, err) + + require.EqualValues(t, now.Unix(), r.UpdatedTime) + require.True(t, r.HasComment) + require.Equal(t, "c", string(r.Comment)) + require.Equal(t, uint8(1), r.Rate) + require.EqualValues(t, now.Unix(), r.DroppedTime) + require.Zero(t, r.WishTime) + require.EqualValues(t, now.Unix(), r.DoingTime) + require.Zero(t, r.DoneTime) + require.Zero(t, r.OnHoldTime) + + // When update to wish state + err = repo.UpdateOrCreateSubjectCollection(context.Background(), uid, subject, now, "", + func(ctx context.Context, s *collection.Subject) (*collection.Subject, error) { + s.UpdateType(collection.SubjectCollectionWish) + require.NoError(t, s.UpdateRate(1)) + return s, nil + }) + require.NoError(t, err) + + r, err = table.WithContext(context.TODO()).Where(table.SubjectID.Eq(sid), table.UserID.Eq(uid)).Take() + require.NoError(t, err) + require.Equal(t, uint8(0), r.Rate) + + // 确认不会影响到其他用户或 subject + r, err = table.WithContext(context.Background()).Where(table.SubjectID.Eq(sid+1), table.UserID.Eq(uid)).Take() + require.NoError(t, err) + + require.EqualValues(t, 8, r.Rate) + + r, err = table.WithContext(context.Background()).Where(table.SubjectID.Eq(sid), table.UserID.Eq(uid+1)).Take() + require.NoError(t, err) + + require.EqualValues(t, 8, r.Rate) + + s, err := q.WithContext(context.Background()).Subject.Preload(q.Subject.Fields).Where(q.Subject.ID.Eq(sid)).First() + require.NoError(t, err) + + tags, err := subject2.ParseTags(s.Fields.Tags) + require.NoError(t, err) + + require.Len(t, tags, 3) +} + func TestMysqlRepo_UpdateSubjectCollection(t *testing.T) { test.RequireEnv(t, test.EnvMysql) t.Parallel() - const uid model.UserID = 40000 - const sid model.SubjectID = 1000 + const uid model.UserID = 35000 + const sid model.SubjectID = 13000 + const subjectType = model.SubjectTypeMusic + + subject := model.Subject{ID: sid, TypeID: subjectType} repo, q := getRepo(t) table := q.SubjectCollection @@ -164,7 +354,7 @@ func TestMysqlRepo_UpdateSubjectCollection(t *testing.T) { now := time.Now() - err = repo.UpdateSubjectCollection(context.Background(), uid, sid, now, "", + err = repo.UpdateSubjectCollection(context.Background(), uid, subject, now, "", func(ctx context.Context, s *collection.Subject) (*collection.Subject, error) { require.NoError(t, s.UpdateComment("c")) require.NoError(t, s.UpdateRate(1)) @@ -197,12 +387,74 @@ func TestMysqlRepo_UpdateSubjectCollection(t *testing.T) { require.EqualValues(t, 8, r.Rate) } +func TestMysqlRepo_UpdateSubjectCollectionType(t *testing.T) { + test.RequireEnv(t, test.EnvMysql) + t.Parallel() + + const uid model.UserID = 36000 + const sid model.SubjectID = 14000 + const subjectType = model.SubjectTypeBook + + subject := model.Subject{ID: sid, TypeID: subjectType} + + repo, q := getRepo(t) + table := q.SubjectCollection + + test.RunAndCleanup(t, func() { + _, err := table.WithContext(context.TODO()).Where(field.Or(table.SubjectID.Eq(sid), table.UserID.Eq(uid))).Delete() + require.NoError(t, err) + }) + + err := table.WithContext(context.Background()).Create( + &dao.SubjectCollection{ + UserID: uid, SubjectID: sid, Rate: 8, Type: uint8(collection.SubjectCollectionDoing), + }, + ) + require.NoError(t, err) + + now := time.Now() + + err = repo.UpdateSubjectCollection(context.Background(), uid, subject, now, "", + func(ctx context.Context, s *collection.Subject) (*collection.Subject, error) { + s.UpdateType(collection.SubjectCollectionDropped) + return s, nil + }) + require.NoError(t, err) + + r, err := table.WithContext(context.TODO()).Where(table.SubjectID.Eq(sid), table.UserID.Eq(uid)).Take() + require.NoError(t, err) + + require.EqualValues(t, uint32(now.Unix()), r.DroppedTime) + require.Zero(t, r.WishTime) + require.Zero(t, r.DoingTime) + require.Zero(t, r.DoneTime) + require.Zero(t, r.OnHoldTime) + + t2 := now.Add(time.Duration(10) * time.Second) + + err = repo.UpdateSubjectCollection(context.Background(), uid, subject, t2, "", + func(ctx context.Context, s *collection.Subject) (*collection.Subject, error) { + s.UpdateType(collection.SubjectCollectionDoing) + return s, nil + }) + require.NoError(t, err) + + r, err = table.WithContext(context.TODO()).Where(table.SubjectID.Eq(sid), table.UserID.Eq(uid)).Take() + require.NoError(t, err) + + require.EqualValues(t, uint32(now.Unix()), r.DroppedTime) + require.EqualValues(t, uint32(t2.Unix()), r.DoingTime) + require.Zero(t, r.WishTime) + require.Zero(t, r.DoneTime) + require.Zero(t, r.OnHoldTime) +} + func TestMysqlRepo_UpdateEpisodeCollection(t *testing.T) { test.RequireEnv(t, test.EnvMysql) t.Parallel() - const uid model.UserID = 40010 - const sid model.SubjectID = 1010 + const uid model.UserID = 37000 + const sid model.SubjectID = 15000 repo, q := getRepo(t) table := q.EpCollection @@ -230,9 +482,9 @@ func TestMysqlRepo_UpdateEpisodeCollection(t *testing.T) { require.EqualValues(t, now.Unix(), r.UpdatedTime) var m map[uint32]struct { - Type int `php:"type"` + Type int `php:"type" json:"type"` } - require.NoError(t, phpserialize.Unmarshal(r.Status, &m)) + require.NoError(t, serialize.Decode(r.Status, &m)) require.Len(t, m, 2) require.Contains(t, m, uint32(1)) require.EqualValues(t, collection.EpisodeCollectionDone, m[1].Type) @@ -250,7 +502,7 @@ func TestMysqlRepo_UpdateEpisodeCollection(t *testing.T) { var m2 map[uint32]struct { Type int `php:"type"` } - require.NoError(t, phpserialize.Unmarshal(r.Status, &m2)) + require.NoError(t, serialize.Decode(r.Status, &m2)) require.Len(t, m2, 0) } @@ -260,8 +512,8 @@ func TestMysqlRepo_UpdateEpisodeCollection_create_ep_status(t *testing.T) { test.RequireEnv(t, test.EnvMysql) t.Parallel() - const uid model.UserID = 40010 - const sid model.SubjectID = 1011 + const uid model.UserID = 38000 + const sid model.SubjectID = 16000 repo, q := getRepo(t) table := q.EpCollection @@ -282,10 +534,204 @@ func TestMysqlRepo_UpdateEpisodeCollection_create_ep_status(t *testing.T) { var m map[uint32]struct { Type int `php:"type"` } - require.NoError(t, phpserialize.Unmarshal(r.Status, &m)) + require.NoError(t, serialize.Decode(r.Status, &m)) require.Len(t, m, 2) require.Contains(t, m, uint32(1)) require.EqualValues(t, collection.EpisodeCollectionDone, m[1].Type) require.Contains(t, m, uint32(2)) require.EqualValues(t, collection.EpisodeCollectionDone, m[2].Type) } + +func TestMysqlRepo_GetPersonCollect(t *testing.T) { + test.RequireEnv(t, test.EnvMysql) + t.Parallel() + + const uid model.UserID = 39000 + const cat = "prsn" + const mid model.PersonID = 12000 + + repo, q := getRepo(t) + test.RunAndCleanup(t, func() { + _, err := q.PersonCollect.WithContext(context.TODO()).Where(q.PersonCollect.UserID.Eq(uid)).Delete() + require.NoError(t, err) + }) + + err := q.PersonCollect.WithContext(context.Background()).Create(&dao.PersonCollect{ + UserID: uid, + Category: cat, + TargetID: mid, + CreatedTime: uint32(time.Now().Unix()), + }) + require.NoError(t, err) + + r, err := repo.GetPersonCollection(context.Background(), uid, cat, mid) + require.NoError(t, err) + require.Equal(t, uid, r.UserID) + require.Equal(t, mid, r.TargetID) + require.Equal(t, cat, r.Category) +} + +func TestMysqlRepo_AddPersonCollect(t *testing.T) { + test.RequireEnv(t, test.EnvMysql) + t.Parallel() + + const uid model.UserID = 40000 + const cat = "prsn" + const mid model.PersonID = 13000 + const collects uint32 = 10 + + repo, q := getRepo(t) + table := q.PersonCollect + test.RunAndCleanup(t, func() { + _, err := table.WithContext(context.TODO()).Where(table.UserID.Eq(uid)).Delete() + require.NoError(t, err) + _, err = q.Person.WithContext(context.TODO()).Where(q.Person.ID.Eq(mid)).Delete() + require.NoError(t, err) + }) + + err := q.Person.WithContext(context.Background()).Create(&dao.Person{ + ID: mid, + Collects: collects, + }) + require.NoError(t, err) + + err = repo.AddPersonCollection(context.Background(), uid, cat, mid) + require.NoError(t, err) + + r, err := table.WithContext(context.TODO()).Where(table.UserID.Eq(uid)).Take() + require.NoError(t, err) + require.NotZero(t, r.ID) + + p, err := q.Person.WithContext(context.Background()).Where(q.Person.ID.Eq(mid)).Take() + require.NoError(t, err) + require.Equal(t, collects+1, p.Collects) +} + +func TestMysqlRepo_RemovePersonCollect(t *testing.T) { + test.RequireEnv(t, test.EnvMysql) + t.Parallel() + + const uid model.UserID = 41000 + const cat = "prsn" + const mid model.PersonID = 14000 + const collects uint32 = 10 + + repo, q := getRepo(t) + test.RunAndCleanup(t, func() { + _, err := q.PersonCollect.WithContext(context.TODO()).Where(q.PersonCollect.UserID.Eq(uid)).Delete() + require.NoError(t, err) + _, err = q.Person.WithContext(context.TODO()).Where(q.Person.ID.Eq(mid)).Delete() + require.NoError(t, err) + }) + + err := q.Person.WithContext(context.Background()).Create(&dao.Person{ + ID: mid, + Collects: collects, + }) + require.NoError(t, err) + err = q.PersonCollect.WithContext(context.Background()).Create(&dao.PersonCollect{ + UserID: uid, + Category: cat, + TargetID: mid, + CreatedTime: uint32(time.Now().Unix()), + }) + require.NoError(t, err) + + r, err := q.PersonCollect.WithContext(context.TODO()).Where(q.PersonCollect.UserID.Eq(uid)).Take() + require.NoError(t, err) + require.NotZero(t, r.ID) + + err = repo.RemovePersonCollection(context.Background(), uid, cat, mid) + require.NoError(t, err) + + _, err = q.PersonCollect.WithContext(context.TODO()).Where(q.PersonCollect.UserID.Eq(uid)).Take() + require.ErrorIs(t, err, gorm.ErrRecordNotFound) + + p, err := q.Person.WithContext(context.Background()).Where(q.Person.ID.Eq(mid)).Take() + require.NoError(t, err) + require.Equal(t, collects-1, p.Collects) +} + +func TestMysqlRepo_CountPersonCollections(t *testing.T) { + t.Parallel() + test.RequireEnv(t, test.EnvMysql) + + const uid model.UserID = 42000 + const cat = "prsn" + + repo, q := getRepo(t) + test.RunAndCleanup(t, func() { + _, err := q.PersonCollect. + WithContext(context.Background()). + Where(q.PersonCollect.UserID.Eq(uid)). + Delete() + require.NoError(t, err) + }) + + for i := 0; i < 5; i++ { + err := q.PersonCollect. + WithContext(context.Background()). + Create(&dao.PersonCollect{ + UserID: uid, + TargetID: model.PersonID(i + 100), + Category: cat, + CreatedTime: uint32(time.Now().Unix()), + }) + require.NoError(t, err) + } + + count, err := repo.CountPersonCollections(context.Background(), uid, cat) + require.NoError(t, err) + require.EqualValues(t, 5, count) +} + +func TestMysqlRepo_ListPersonCollection(t *testing.T) { + t.Parallel() + test.RequireEnv(t, test.EnvMysql) + + const uid model.UserID = 43000 + const cat = "prsn" + + repo, q := getRepo(t) + + var err error + test.RunAndCleanup(t, func() { + _, err = q.PersonCollect. + WithContext(context.Background()). + Where(q.PersonCollect.UserID.Eq(uid)). + Delete() + require.NoError(t, err) + }) + + data, err := repo.ListPersonCollection(context.Background(), uid, collection.PersonCollectCategory(cat), 5, 0) + require.NoError(t, err) + require.Len(t, data, 0) + + for i := 0; i < 5; i++ { + err = q.PersonCollect. + WithContext(context.Background()). + Create(&dao.PersonCollect{ + UserID: uid, + TargetID: model.PersonID(i + 100), + Category: cat, + CreatedTime: uint32(time.Now().Unix()), + }) + require.NoError(t, err) + } + + for i := 0; i < 2; i++ { + err = q.PersonCollect. + WithContext(context.Background()). + Create(&dao.PersonCollect{ + UserID: uid, + TargetID: model.PersonID(i + 200), + Category: cat, + CreatedTime: uint32(time.Now().Unix()), + }) + require.NoError(t, err) + } + + data, err = repo.ListPersonCollection(context.Background(), uid, collection.PersonCollectCategory(cat), 5, 0) + require.NoError(t, err) + require.Len(t, data, 5) +} diff --git a/internal/episode/mysql_repository_test.go b/internal/episode/mysql_repository_test.go index 89acf772b..418d10a70 100644 --- a/internal/episode/mysql_repository_test.go +++ b/internal/episode/mysql_repository_test.go @@ -105,3 +105,19 @@ func TestMysqlRepo_List(t *testing.T) { require.Len(t, episodes, tc.len) } } + +func TestMysqlRepo_List_Limit(t *testing.T) { + test.RequireEnv(t, test.EnvMysql) + t.Parallel() + + repo := getRepo(t) + + nums := []int{-1, 0, 10, 22, 30, 100} + expected := []int{31, 0, 10, 22, 30, 31} + + for i, num := range nums { + episodes, err := repo.List(context.TODO(), 253, episode.Filter{}, num, 0) + require.NoError(t, err) + require.Len(t, episodes, expected[i]) + } +} diff --git a/internal/index/mysql_repository.go b/internal/index/mysql_repository.go index bf618b38d..1267a63f9 100644 --- a/internal/index/mysql_repository.go +++ b/internal/index/mysql_repository.go @@ -19,6 +19,7 @@ import ( "errors" "time" + "github.com/jmoiron/sqlx" "github.com/trim21/errgo" "go.uber.org/zap" "gorm.io/gen" @@ -31,19 +32,20 @@ import ( "github.com/bangumi/server/internal/subject" ) -func NewMysqlRepo(q *query.Query, log *zap.Logger) (Repo, error) { - return mysqlRepo{q: q, log: log.Named("index.mysqlRepo")}, nil +func NewMysqlRepo(q *query.Query, log *zap.Logger, db *sqlx.DB) (Repo, error) { + return mysqlRepo{q: q, log: log.Named("index.mysqlRepo"), db: db}, nil } type mysqlRepo struct { q *query.Query log *zap.Logger + db *sqlx.DB } func (r mysqlRepo) isNsfw(ctx context.Context, id model.IndexID) (bool, error) { i, err := r.q.IndexSubject.WithContext(ctx). Join(r.q.Subject, r.q.IndexSubject.SubjectID.EqCol(r.q.Subject.ID)). - Where(r.q.IndexSubject.IndexID.Eq(id), r.q.Subject.Nsfw.Is(true)).Count() + Where(r.q.IndexSubject.IndexID.Eq(id), r.q.IndexSubject.Cat.Eq(0), r.q.Subject.Nsfw.Is(true)).Count() if err != nil { r.log.Error("unexpected error when checking index nsfw", zap.Uint32("index_id", id)) return false, errgo.Wrap(err, "dal") @@ -54,7 +56,11 @@ func (r mysqlRepo) isNsfw(ctx context.Context, id model.IndexID) (bool, error) { func (r mysqlRepo) Get(ctx context.Context, id model.IndexID) (model.Index, error) { i, err := r.q.Index.WithContext(ctx). - Where(r.q.Index.ID.Eq(id), r.q.Index.Ban.Is(false)).Take() + Where( + r.q.Index.ID.Eq(id), + r.q.Index.Privacy.Neq(uint8(model.IndexPrivacyDeleted)), + ). + Take() if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return model.Index{}, gerr.ErrNotFound @@ -84,7 +90,10 @@ func (r mysqlRepo) New(ctx context.Context, i *model.Index) error { func (r mysqlRepo) Update(ctx context.Context, id model.IndexID, title string, desc string) error { query := r.q.Index.WithContext(ctx) - result, err := query.Where(r.q.Index.ID.Eq(id)).Updates(dao.Index{ + result, err := query.Where( + r.q.Index.ID.Eq(id), + r.q.Index.Privacy.Neq(uint8(model.IndexPrivacyDeleted)), + ).Updates(dao.Index{ Title: title, Desc: desc, }) @@ -93,19 +102,20 @@ func (r mysqlRepo) Update(ctx context.Context, id model.IndexID, title string, d func (r mysqlRepo) Delete(ctx context.Context, id model.IndexID) error { return r.q.Transaction(func(tx *query.Query) error { - result, err := tx.Index.WithContext(ctx).Where(tx.Index.ID.Eq(id)).Delete() - if err = r.WrapResult(result, err, "failed to delete index"); err != nil { - return err - } - result, err = tx.IndexSubject.WithContext(ctx).Where(tx.IndexSubject.IndexID.Eq(id)).Delete() - return r.WrapResult(result, err, "failed to delete subjects in the index") + result, err := tx.Index.WithContext(ctx). + Where(tx.Index.ID.Eq(id)). + UpdateColumnSimple(tx.Index.Privacy.Value(uint8(model.IndexPrivacyDeleted))) + return r.WrapResult(result, err, "failed to delete index") }) } func (r mysqlRepo) CountSubjects( ctx context.Context, id model.IndexID, subjectType model.SubjectType, ) (int64, error) { - q := r.q.IndexSubject.WithContext(ctx).Where(r.q.IndexSubject.IndexID.Eq(id)) + if _, err := r.Get(ctx, id); err != nil { + return 0, err + } + q := r.q.IndexSubject.WithContext(ctx).Where(r.q.IndexSubject.IndexID.Eq(id), r.q.IndexSubject.Cat.Eq(0)) if subjectType != 0 { q = q.Where(r.q.IndexSubject.SubjectType.Eq(subjectType)) } @@ -124,9 +134,12 @@ func (r mysqlRepo) ListSubjects( subjectType model.SubjectType, limit, offset int, ) ([]Subject, error) { + if _, err := r.Get(ctx, id); err != nil { + return nil, err + } q := r.q.IndexSubject.WithContext(ctx).Joins(r.q.IndexSubject.Subject). Preload(r.q.IndexSubject.Subject.Fields). - Where(r.q.IndexSubject.IndexID.Eq(id)). + Where(r.q.IndexSubject.IndexID.Eq(id), r.q.IndexSubject.Cat.Eq(0)). Order(r.q.IndexSubject.Order). Limit(limit).Offset(offset) if subjectType != 0 { @@ -159,7 +172,7 @@ func (r mysqlRepo) AddOrUpdateIndexSubject( ctx context.Context, id model.IndexID, subjectID model.SubjectID, sort uint32, comment string, ) (*Subject, error) { - index, err := r.Get(ctx, id) + _, err := r.Get(ctx, id) if err != nil { return nil, err } @@ -194,7 +207,7 @@ func (r mysqlRepo) AddOrUpdateIndexSubject( // 已经存在,更新! err = r.updateIndexSubject(ctx, id, subjectID, sort, comment) } else { - err = r.addSubjectToIndex(ctx, index, &dao.IndexSubject{ + err = r.addSubjectToIndex(ctx, &dao.IndexSubject{ Comment: comment, Order: sort, CreatedTime: uint32(now.Unix()), @@ -221,50 +234,49 @@ func (r mysqlRepo) updateIndexSubject( ) error { result, err := r.q.IndexSubject.WithContext(ctx). Where(r.q.IndexSubject.IndexID.Eq(id), r.q.IndexSubject.SubjectID.Eq(subjectID)). - Updates(dao.IndexSubject{ - Order: sort, - Comment: comment, - }) + UpdateColumnSimple(r.q.IndexSubject.Order.Value(sort), r.q.IndexSubject.Comment.Value(comment)) return r.WrapResult(result, err, "failed to update index subject") } -func (r mysqlRepo) addSubjectToIndex(ctx context.Context, index model.Index, subject *dao.IndexSubject) error { - return r.q.Transaction(func(tx *query.Query) error { - err := r.q.IndexSubject.WithContext(ctx).Create(subject) - if err != nil { - return errgo.Wrap(err, "failed to create subject in index") - } +func (r mysqlRepo) addSubjectToIndex(ctx context.Context, subject *dao.IndexSubject) error { + err := r.q.IndexSubject.WithContext(ctx).Create(subject) + if err != nil { + return errgo.Wrap(err, "failed to create subject in index") + } - result, err := r.q.Index.WithContext(ctx). - Where(r.q.Index.ID.Eq(index.ID)). - Updates(dao.Index{ - UpdatedTime: uint32(time.Now().Unix()), - SubjectCount: index.Total + 1, - }) + return r.countSubjectTotal(ctx, subject.IndexID) +} - return r.WrapResult(result, err, "failed to update index info") - }) +func (r mysqlRepo) countSubjectTotal(ctx context.Context, index model.IndexID) error { + _, err := r.db.ExecContext(ctx, ` + update chii_index set + idx_subject_total = ( + select count(1) + from chii_index_related as tl + where tl.idx_rlt_ban = 0 AND tl.idx_rlt_rid = ? + ), + idx_lasttouch = ? + where idx_id = ? + `, index, time.Now().Unix(), index) + + return errgo.Wrap(err, "failed to update index info") } func (r mysqlRepo) DeleteIndexSubject( ctx context.Context, id model.IndexID, subjectID model.SubjectID, ) error { - return r.q.Transaction(func(tx *query.Query) error { - index, err := r.Get(ctx, id) - if err != nil { - return err - } - result, err := r.q.IndexSubject.WithContext(ctx). - Where(r.q.IndexSubject.IndexID.Eq(id), r.q.IndexSubject.SubjectID.Eq(subjectID)). - Delete() - if err = r.WrapResult(result, err, "failed to delete index subject"); err != nil { - return err - } - result, err = r.q.Index.WithContext(ctx).Where(r.q.Index.ID.Eq(id)).Updates(dao.Index{ - SubjectCount: index.Total - 1, - }) - return r.WrapResult(result, err, "failed to update index info") - }) + _, err := r.Get(ctx, id) + if err != nil { + return err + } + + result, err := r.q.IndexSubject.WithContext(ctx). + Where(r.q.IndexSubject.IndexID.Eq(id), r.q.IndexSubject.SubjectID.Eq(subjectID)). + Delete() + if err = r.WrapResult(result, err, "failed to delete index subject"); err != nil { + return err + } + return r.countSubjectTotal(ctx, id) } func (r mysqlRepo) GetIndexCollect(ctx context.Context, id model.IndexID, uid model.UserID) (*IndexCollect, error) { @@ -335,8 +347,8 @@ func daoToModel(index *dao.Index) *model.Index { Total: index.SubjectCount, Comments: index.ReplyCount, Collects: index.CollectCount, - Ban: index.Ban, NSFW: false, // check nsfw outSubjectIDe of this function + Privacy: model.IndexPrivacy(index.Privacy), CreatedAt: time.Unix(int64(index.CreatedTime), 0), UpdatedAt: time.Unix(int64(index.UpdatedTime), 0), } @@ -349,8 +361,8 @@ func modelToDAO(index *model.Index) *dao.Index { Title: index.Title, Desc: index.Description, CreatorID: index.CreatorID, - Ban: index.Ban, CreatedTime: int32(index.CreatedAt.Unix()), UpdatedTime: uint32(index.UpdatedAt.Unix()), + Privacy: uint8(index.Privacy), } } diff --git a/internal/index/mysql_repository_test.go b/internal/index/mysql_repository_test.go index c87a29f44..d6faa12ec 100644 --- a/internal/index/mysql_repository_test.go +++ b/internal/index/mysql_repository_test.go @@ -20,6 +20,8 @@ import ( "testing" "time" + "github.com/jmoiron/sqlx" + "github.com/samber/lo" "github.com/stretchr/testify/require" "go.uber.org/zap" @@ -32,7 +34,8 @@ import ( func getRepo(t *testing.T) index.Repo { t.Helper() - repo, err := index.NewMysqlRepo(query.Use(test.GetGorm(t)), zap.NewNop()) + q := query.Use(test.GetGorm(t)) + repo, err := index.NewMysqlRepo(q, zap.NewNop(), sqlx.NewDb(lo.Must(q.DB().DB()), "mysql")) require.NoError(t, err) return repo @@ -52,6 +55,58 @@ func TestMysqlRepo_Get(t *testing.T) { require.False(t, i.NSFW) } +func TestMysqlRepo_GetPrivateIndex(t *testing.T) { + test.RequireEnv(t, test.EnvMysql) + t.Parallel() + + repo := getRepo(t) + ctx := context.Background() + now := time.Now() + + idx := &model.Index{ + ID: 0, + Title: "private index", + Description: "private visibility", + CreatorID: 382951, + CreatedAt: now, + UpdatedAt: now, + Privacy: model.IndexPrivacyPrivate, + } + require.NoError(t, repo.New(ctx, idx)) + defer func() { _ = repo.Delete(ctx, idx.ID) }() + + got, err := repo.Get(ctx, idx.ID) + require.NoError(t, err) + require.Equal(t, idx.ID, got.ID) + require.Equal(t, model.IndexPrivacyPrivate, got.Privacy) +} + +func TestMysqlRepo_GetDeletedIndex(t *testing.T) { + test.RequireEnv(t, test.EnvMysql) + t.Parallel() + + repo := getRepo(t) + ctx := context.Background() + now := time.Now() + + idx := &model.Index{ + ID: 0, + Title: "deleted index", + Description: "deleted visibility", + CreatorID: 382951, + CreatedAt: now, + UpdatedAt: now, + Privacy: model.IndexPrivacyPublic, + } + require.NoError(t, repo.New(ctx, idx)) + defer func() { _ = repo.Delete(ctx, idx.ID) }() + + require.NoError(t, repo.Delete(ctx, idx.ID)) + + _, err := repo.Get(ctx, idx.ID) + require.ErrorIs(t, err, gerr.ErrNotFound) +} + func TestMysqlRepo_ListSubjects(t *testing.T) { test.RequireEnv(t, test.EnvMysql) t.Parallel() @@ -83,7 +138,6 @@ func TestMysqlRepo_NewIndex(t *testing.T) { Total: 0, Comments: 0, Collects: 0, - Ban: false, NSFW: false, } err := repo.New(context.Background(), index) @@ -118,7 +172,6 @@ func TestMysqlRepo_UpdateIndex(t *testing.T) { Total: 0, Comments: 0, Collects: 0, - Ban: false, NSFW: false, } err := repo.New(ctx, index) @@ -151,7 +204,6 @@ func TestMysqlRepo_DeleteIndex(t *testing.T) { Total: 0, Comments: 0, Collects: 0, - Ban: false, NSFW: false, } _ = repo.New(context.Background(), index) @@ -184,7 +236,6 @@ func TestMysqlRepo_DeleteIndex2(t *testing.T) { Total: 0, Comments: 0, Collects: 0, - Ban: false, NSFW: false, } @@ -193,9 +244,8 @@ func TestMysqlRepo_DeleteIndex2(t *testing.T) { err := repo.New(ctx, index) require.NoError(t, err) - for i := 10; i < 20; i++ { - _, err = repo.AddOrUpdateIndexSubject(ctx, index.ID, model.SubjectID(i), - uint32(i), fmt.Sprintf("comment %d", i)) + for i := uint32(10); i < 20; i++ { + _, err = repo.AddOrUpdateIndexSubject(ctx, index.ID, i, i, fmt.Sprintf("comment %d", i)) require.NoError(t, err) } @@ -214,8 +264,8 @@ func TestMysqlRepo_DeleteIndex2(t *testing.T) { require.Equal(t, err, gerr.ErrNotFound) subjects, err = repo.ListSubjects(context.Background(), index.ID, model.SubjectTypeAll, 20, 0) - require.NoError(t, err) - require.Len(t, subjects, 0) + require.ErrorIs(t, err, gerr.ErrNotFound) + require.Nil(t, subjects) // 确保不会影响到其他目录 subjects, err = repo.ListSubjects(context.Background(), 15045, model.SubjectTypeAll, 20, 0) @@ -237,7 +287,6 @@ func TestMysqlRepo_AddOrUpdateIndexSubject(t *testing.T) { Total: 0, Comments: 0, Collects: 0, - Ban: false, NSFW: false, } @@ -288,7 +337,6 @@ func TestMysqlRepo_DeleteIndexSubject(t *testing.T) { Total: 0, Comments: 0, Collects: 0, - Ban: false, NSFW: false, } @@ -298,9 +346,8 @@ func TestMysqlRepo_DeleteIndexSubject(t *testing.T) { require.NotEqual(t, 0, index.ID) require.NoError(t, err) - for i := 10; i < 20; i++ { - _, err = repo.AddOrUpdateIndexSubject(ctx, index.ID, model.SubjectID(i), - uint32(i), fmt.Sprintf("comment %d", i)) + for i := uint32(10); i < 20; i++ { + _, err = repo.AddOrUpdateIndexSubject(ctx, index.ID, i, i, fmt.Sprintf("comment %d", i)) require.NoError(t, err) } @@ -374,7 +421,6 @@ func TestMysqlRepo_UpdateSubjectInfo(t *testing.T) { Total: 0, Comments: 0, Collects: 0, - Ban: false, NSFW: false, } ctx := context.Background() @@ -415,7 +461,6 @@ func TestMysqlRepo_AddExists(t *testing.T) { Total: 0, Comments: 0, Collects: 0, - Ban: false, NSFW: false, } ctx := context.Background() diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index 410f90e5c..9756532f9 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -12,7 +12,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see -//nolint:gomnd,gochecknoinits,gochecknoglobals +//nolint:mnd,gochecknoinits,gochecknoglobals package metrics import ( @@ -45,7 +45,10 @@ var RequestHistogram = prometheus.NewHistogram(prometheus.HistogramOpts{ 0.200, 0.300, 0.500, - 1.000, + 1, + 2, + 5, + 10, }, }) @@ -68,6 +71,9 @@ var SQLHistogram = prometheus.NewHistogram(prometheus.HistogramOpts{ 0.200, 0.300, 0.500, - 1.000, + 1, + 2, + 5, + 10, }, }) diff --git a/internal/metrics/redis.go b/internal/metrics/redis.go deleted file mode 100644 index 12fa990bb..000000000 --- a/internal/metrics/redis.go +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package metrics - -import ( - "github.com/redis/go-redis/v9" - redisprom "github.com/trim21/go-redis-prometheus" -) - -func RedisHook(instance string) redis.Hook { - return redisprom.NewHook( - redisprom.WithNamespace("chii"), - redisprom.WithDurationBuckets([]float64{.001, .002, .003, .004, .005, .0075, .01, .05, .1, .3, .5, .75, 1, 2}), - redisprom.WithInstanceName(instance), - ) -} diff --git a/internal/mocks/AuthRepo.go b/internal/mocks/AuthRepo.go index 4e4790758..4e5d86c38 100644 --- a/internal/mocks/AuthRepo.go +++ b/internal/mocks/AuthRepo.go @@ -1,17 +1,30 @@ -// Code generated by mockery v2.23.1. DO NOT EDIT. +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify package mocks import ( - context "context" - - auth "github.com/bangumi/server/internal/auth" + "context" + "github.com/bangumi/server/internal/auth" mock "github.com/stretchr/testify/mock" - - time "time" ) +// NewAuthRepo creates a new instance of AuthRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewAuthRepo(t interface { + mock.TestingT + Cleanup(func()) +}) *AuthRepo { + mock := &AuthRepo{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + // AuthRepo is an autogenerated mock type for the Repo type type AuthRepo struct { mock.Mock @@ -25,197 +38,29 @@ func (_m *AuthRepo) EXPECT() *AuthRepo_Expecter { return &AuthRepo_Expecter{mock: &_m.Mock} } -// CreateAccessToken provides a mock function with given fields: ctx, userID, name, expiration -func (_m *AuthRepo) CreateAccessToken(ctx context.Context, userID uint32, name string, expiration time.Duration) (string, error) { - ret := _m.Called(ctx, userID, name, expiration) - - var r0 string - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, string, time.Duration) (string, error)); ok { - return rf(ctx, userID, name, expiration) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32, string, time.Duration) string); ok { - r0 = rf(ctx, userID, name, expiration) - } else { - r0 = ret.Get(0).(string) - } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, string, time.Duration) error); ok { - r1 = rf(ctx, userID, name, expiration) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// AuthRepo_CreateAccessToken_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateAccessToken' -type AuthRepo_CreateAccessToken_Call struct { - *mock.Call -} - -// CreateAccessToken is a helper method to define mock.On call -// - ctx context.Context -// - userID uint32 -// - name string -// - expiration time.Duration -func (_e *AuthRepo_Expecter) CreateAccessToken(ctx interface{}, userID interface{}, name interface{}, expiration interface{}) *AuthRepo_CreateAccessToken_Call { - return &AuthRepo_CreateAccessToken_Call{Call: _e.mock.On("CreateAccessToken", ctx, userID, name, expiration)} -} - -func (_c *AuthRepo_CreateAccessToken_Call) Run(run func(ctx context.Context, userID uint32, name string, expiration time.Duration)) *AuthRepo_CreateAccessToken_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(string), args[3].(time.Duration)) - }) - return _c -} - -func (_c *AuthRepo_CreateAccessToken_Call) Return(token string, err error) *AuthRepo_CreateAccessToken_Call { - _c.Call.Return(token, err) - return _c -} - -func (_c *AuthRepo_CreateAccessToken_Call) RunAndReturn(run func(context.Context, uint32, string, time.Duration) (string, error)) *AuthRepo_CreateAccessToken_Call { - _c.Call.Return(run) - return _c -} - -// DeleteAccessToken provides a mock function with given fields: ctx, tokenID -func (_m *AuthRepo) DeleteAccessToken(ctx context.Context, tokenID uint32) (bool, error) { - ret := _m.Called(ctx, tokenID) - - var r0 bool - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (bool, error)); ok { - return rf(ctx, tokenID) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32) bool); ok { - r0 = rf(ctx, tokenID) - } else { - r0 = ret.Get(0).(bool) - } +// GetByToken provides a mock function for the type AuthRepo +func (_mock *AuthRepo) GetByToken(ctx context.Context, token string) (auth.UserInfo, error) { + ret := _mock.Called(ctx, token) - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, tokenID) - } else { - r1 = ret.Error(1) + if len(ret) == 0 { + panic("no return value specified for GetByToken") } - return r0, r1 -} - -// AuthRepo_DeleteAccessToken_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteAccessToken' -type AuthRepo_DeleteAccessToken_Call struct { - *mock.Call -} - -// DeleteAccessToken is a helper method to define mock.On call -// - ctx context.Context -// - tokenID uint32 -func (_e *AuthRepo_Expecter) DeleteAccessToken(ctx interface{}, tokenID interface{}) *AuthRepo_DeleteAccessToken_Call { - return &AuthRepo_DeleteAccessToken_Call{Call: _e.mock.On("DeleteAccessToken", ctx, tokenID)} -} - -func (_c *AuthRepo_DeleteAccessToken_Call) Run(run func(ctx context.Context, tokenID uint32)) *AuthRepo_DeleteAccessToken_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) - }) - return _c -} - -func (_c *AuthRepo_DeleteAccessToken_Call) Return(_a0 bool, _a1 error) *AuthRepo_DeleteAccessToken_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *AuthRepo_DeleteAccessToken_Call) RunAndReturn(run func(context.Context, uint32) (bool, error)) *AuthRepo_DeleteAccessToken_Call { - _c.Call.Return(run) - return _c -} - -// GetByEmail provides a mock function with given fields: ctx, email -func (_m *AuthRepo) GetByEmail(ctx context.Context, email string) (auth.UserInfo, []byte, error) { - ret := _m.Called(ctx, email) - - var r0 auth.UserInfo - var r1 []byte - var r2 error - if rf, ok := ret.Get(0).(func(context.Context, string) (auth.UserInfo, []byte, error)); ok { - return rf(ctx, email) - } - if rf, ok := ret.Get(0).(func(context.Context, string) auth.UserInfo); ok { - r0 = rf(ctx, email) - } else { - r0 = ret.Get(0).(auth.UserInfo) - } - - if rf, ok := ret.Get(1).(func(context.Context, string) []byte); ok { - r1 = rf(ctx, email) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).([]byte) - } - } - - if rf, ok := ret.Get(2).(func(context.Context, string) error); ok { - r2 = rf(ctx, email) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// AuthRepo_GetByEmail_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByEmail' -type AuthRepo_GetByEmail_Call struct { - *mock.Call -} - -// GetByEmail is a helper method to define mock.On call -// - ctx context.Context -// - email string -func (_e *AuthRepo_Expecter) GetByEmail(ctx interface{}, email interface{}) *AuthRepo_GetByEmail_Call { - return &AuthRepo_GetByEmail_Call{Call: _e.mock.On("GetByEmail", ctx, email)} -} - -func (_c *AuthRepo_GetByEmail_Call) Run(run func(ctx context.Context, email string)) *AuthRepo_GetByEmail_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) - }) - return _c -} - -func (_c *AuthRepo_GetByEmail_Call) Return(_a0 auth.UserInfo, _a1 []byte, _a2 error) *AuthRepo_GetByEmail_Call { - _c.Call.Return(_a0, _a1, _a2) - return _c -} - -func (_c *AuthRepo_GetByEmail_Call) RunAndReturn(run func(context.Context, string) (auth.UserInfo, []byte, error)) *AuthRepo_GetByEmail_Call { - _c.Call.Return(run) - return _c -} - -// GetByToken provides a mock function with given fields: ctx, token -func (_m *AuthRepo) GetByToken(ctx context.Context, token string) (auth.UserInfo, error) { - ret := _m.Called(ctx, token) - var r0 auth.UserInfo var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (auth.UserInfo, error)); ok { - return rf(ctx, token) + if returnFunc, ok := ret.Get(0).(func(context.Context, string) (auth.UserInfo, error)); ok { + return returnFunc(ctx, token) } - if rf, ok := ret.Get(0).(func(context.Context, string) auth.UserInfo); ok { - r0 = rf(ctx, token) + if returnFunc, ok := ret.Get(0).(func(context.Context, string) auth.UserInfo); ok { + r0 = returnFunc(ctx, token) } else { r0 = ret.Get(0).(auth.UserInfo) } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, token) + if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = returnFunc(ctx, token) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -233,42 +78,55 @@ func (_e *AuthRepo_Expecter) GetByToken(ctx interface{}, token interface{}) *Aut func (_c *AuthRepo_GetByToken_Call) Run(run func(ctx context.Context, token string)) *AuthRepo_GetByToken_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *AuthRepo_GetByToken_Call) Return(_a0 auth.UserInfo, _a1 error) *AuthRepo_GetByToken_Call { - _c.Call.Return(_a0, _a1) +func (_c *AuthRepo_GetByToken_Call) Return(userInfo auth.UserInfo, err error) *AuthRepo_GetByToken_Call { + _c.Call.Return(userInfo, err) return _c } -func (_c *AuthRepo_GetByToken_Call) RunAndReturn(run func(context.Context, string) (auth.UserInfo, error)) *AuthRepo_GetByToken_Call { +func (_c *AuthRepo_GetByToken_Call) RunAndReturn(run func(ctx context.Context, token string) (auth.UserInfo, error)) *AuthRepo_GetByToken_Call { _c.Call.Return(run) return _c } -// GetPermission provides a mock function with given fields: ctx, groupID -func (_m *AuthRepo) GetPermission(ctx context.Context, groupID uint8) (auth.Permission, error) { - ret := _m.Called(ctx, groupID) +// GetPermission provides a mock function for the type AuthRepo +func (_mock *AuthRepo) GetPermission(ctx context.Context, groupID uint8) (auth.Permission, error) { + ret := _mock.Called(ctx, groupID) + + if len(ret) == 0 { + panic("no return value specified for GetPermission") + } var r0 auth.Permission var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint8) (auth.Permission, error)); ok { - return rf(ctx, groupID) + if returnFunc, ok := ret.Get(0).(func(context.Context, uint8) (auth.Permission, error)); ok { + return returnFunc(ctx, groupID) } - if rf, ok := ret.Get(0).(func(context.Context, uint8) auth.Permission); ok { - r0 = rf(ctx, groupID) + if returnFunc, ok := ret.Get(0).(func(context.Context, uint8) auth.Permission); ok { + r0 = returnFunc(ctx, groupID) } else { r0 = ret.Get(0).(auth.Permission) } - - if rf, ok := ret.Get(1).(func(context.Context, uint8) error); ok { - r1 = rf(ctx, groupID) + if returnFunc, ok := ret.Get(1).(func(context.Context, uint8) error); ok { + r1 = returnFunc(ctx, groupID) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -286,140 +144,28 @@ func (_e *AuthRepo_Expecter) GetPermission(ctx interface{}, groupID interface{}) func (_c *AuthRepo_GetPermission_Call) Run(run func(ctx context.Context, groupID uint8)) *AuthRepo_GetPermission_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint8)) - }) - return _c -} - -func (_c *AuthRepo_GetPermission_Call) Return(_a0 auth.Permission, _a1 error) *AuthRepo_GetPermission_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *AuthRepo_GetPermission_Call) RunAndReturn(run func(context.Context, uint8) (auth.Permission, error)) *AuthRepo_GetPermission_Call { - _c.Call.Return(run) - return _c -} - -// GetTokenByID provides a mock function with given fields: ctx, id -func (_m *AuthRepo) GetTokenByID(ctx context.Context, id uint32) (auth.AccessToken, error) { - ret := _m.Called(ctx, id) - - var r0 auth.AccessToken - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (auth.AccessToken, error)); ok { - return rf(ctx, id) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32) auth.AccessToken); ok { - r0 = rf(ctx, id) - } else { - r0 = ret.Get(0).(auth.AccessToken) - } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// AuthRepo_GetTokenByID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTokenByID' -type AuthRepo_GetTokenByID_Call struct { - *mock.Call -} - -// GetTokenByID is a helper method to define mock.On call -// - ctx context.Context -// - id uint32 -func (_e *AuthRepo_Expecter) GetTokenByID(ctx interface{}, id interface{}) *AuthRepo_GetTokenByID_Call { - return &AuthRepo_GetTokenByID_Call{Call: _e.mock.On("GetTokenByID", ctx, id)} -} - -func (_c *AuthRepo_GetTokenByID_Call) Run(run func(ctx context.Context, id uint32)) *AuthRepo_GetTokenByID_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) - }) - return _c -} - -func (_c *AuthRepo_GetTokenByID_Call) Return(_a0 auth.AccessToken, _a1 error) *AuthRepo_GetTokenByID_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *AuthRepo_GetTokenByID_Call) RunAndReturn(run func(context.Context, uint32) (auth.AccessToken, error)) *AuthRepo_GetTokenByID_Call { - _c.Call.Return(run) - return _c -} - -// ListAccessToken provides a mock function with given fields: ctx, userID -func (_m *AuthRepo) ListAccessToken(ctx context.Context, userID uint32) ([]auth.AccessToken, error) { - ret := _m.Called(ctx, userID) - - var r0 []auth.AccessToken - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) ([]auth.AccessToken, error)); ok { - return rf(ctx, userID) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32) []auth.AccessToken); ok { - r0 = rf(ctx, userID) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]auth.AccessToken) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) } - } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, userID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// AuthRepo_ListAccessToken_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListAccessToken' -type AuthRepo_ListAccessToken_Call struct { - *mock.Call -} - -// ListAccessToken is a helper method to define mock.On call -// - ctx context.Context -// - userID uint32 -func (_e *AuthRepo_Expecter) ListAccessToken(ctx interface{}, userID interface{}) *AuthRepo_ListAccessToken_Call { - return &AuthRepo_ListAccessToken_Call{Call: _e.mock.On("ListAccessToken", ctx, userID)} -} - -func (_c *AuthRepo_ListAccessToken_Call) Run(run func(ctx context.Context, userID uint32)) *AuthRepo_ListAccessToken_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg1 uint8 + if args[1] != nil { + arg1 = args[1].(uint8) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *AuthRepo_ListAccessToken_Call) Return(_a0 []auth.AccessToken, _a1 error) *AuthRepo_ListAccessToken_Call { - _c.Call.Return(_a0, _a1) +func (_c *AuthRepo_GetPermission_Call) Return(permission auth.Permission, err error) *AuthRepo_GetPermission_Call { + _c.Call.Return(permission, err) return _c } -func (_c *AuthRepo_ListAccessToken_Call) RunAndReturn(run func(context.Context, uint32) ([]auth.AccessToken, error)) *AuthRepo_ListAccessToken_Call { +func (_c *AuthRepo_GetPermission_Call) RunAndReturn(run func(ctx context.Context, groupID uint8) (auth.Permission, error)) *AuthRepo_GetPermission_Call { _c.Call.Return(run) return _c } - -type mockConstructorTestingTNewAuthRepo interface { - mock.TestingT - Cleanup(func()) -} - -// NewAuthRepo creates a new instance of AuthRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewAuthRepo(t mockConstructorTestingTNewAuthRepo) *AuthRepo { - mock := &AuthRepo{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/mocks/AuthService.go b/internal/mocks/AuthService.go index 586861410..cf512b90f 100644 --- a/internal/mocks/AuthService.go +++ b/internal/mocks/AuthService.go @@ -1,17 +1,30 @@ -// Code generated by mockery v2.23.1. DO NOT EDIT. +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify package mocks import ( - context "context" - - auth "github.com/bangumi/server/internal/auth" + "context" + "github.com/bangumi/server/internal/auth" mock "github.com/stretchr/testify/mock" - - time "time" ) +// NewAuthService creates a new instance of AuthService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewAuthService(t interface { + mock.TestingT + Cleanup(func()) +}) *AuthService { + mock := &AuthService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + // AuthService is an autogenerated mock type for the Service type type AuthService struct { mock.Mock @@ -25,241 +38,29 @@ func (_m *AuthService) EXPECT() *AuthService_Expecter { return &AuthService_Expecter{mock: &_m.Mock} } -// ComparePassword provides a mock function with given fields: hashed, password -func (_m *AuthService) ComparePassword(hashed []byte, password string) (bool, error) { - ret := _m.Called(hashed, password) - - var r0 bool - var r1 error - if rf, ok := ret.Get(0).(func([]byte, string) (bool, error)); ok { - return rf(hashed, password) - } - if rf, ok := ret.Get(0).(func([]byte, string) bool); ok { - r0 = rf(hashed, password) - } else { - r0 = ret.Get(0).(bool) - } - - if rf, ok := ret.Get(1).(func([]byte, string) error); ok { - r1 = rf(hashed, password) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// AuthService_ComparePassword_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ComparePassword' -type AuthService_ComparePassword_Call struct { - *mock.Call -} - -// ComparePassword is a helper method to define mock.On call -// - hashed []byte -// - password string -func (_e *AuthService_Expecter) ComparePassword(hashed interface{}, password interface{}) *AuthService_ComparePassword_Call { - return &AuthService_ComparePassword_Call{Call: _e.mock.On("ComparePassword", hashed, password)} -} - -func (_c *AuthService_ComparePassword_Call) Run(run func(hashed []byte, password string)) *AuthService_ComparePassword_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].([]byte), args[1].(string)) - }) - return _c -} - -func (_c *AuthService_ComparePassword_Call) Return(_a0 bool, _a1 error) *AuthService_ComparePassword_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *AuthService_ComparePassword_Call) RunAndReturn(run func([]byte, string) (bool, error)) *AuthService_ComparePassword_Call { - _c.Call.Return(run) - return _c -} - -// CreateAccessToken provides a mock function with given fields: ctx, userID, name, expiration -func (_m *AuthService) CreateAccessToken(ctx context.Context, userID uint32, name string, expiration time.Duration) (string, error) { - ret := _m.Called(ctx, userID, name, expiration) - - var r0 string - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, string, time.Duration) (string, error)); ok { - return rf(ctx, userID, name, expiration) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32, string, time.Duration) string); ok { - r0 = rf(ctx, userID, name, expiration) - } else { - r0 = ret.Get(0).(string) - } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, string, time.Duration) error); ok { - r1 = rf(ctx, userID, name, expiration) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// AuthService_CreateAccessToken_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateAccessToken' -type AuthService_CreateAccessToken_Call struct { - *mock.Call -} - -// CreateAccessToken is a helper method to define mock.On call -// - ctx context.Context -// - userID uint32 -// - name string -// - expiration time.Duration -func (_e *AuthService_Expecter) CreateAccessToken(ctx interface{}, userID interface{}, name interface{}, expiration interface{}) *AuthService_CreateAccessToken_Call { - return &AuthService_CreateAccessToken_Call{Call: _e.mock.On("CreateAccessToken", ctx, userID, name, expiration)} -} - -func (_c *AuthService_CreateAccessToken_Call) Run(run func(ctx context.Context, userID uint32, name string, expiration time.Duration)) *AuthService_CreateAccessToken_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(string), args[3].(time.Duration)) - }) - return _c -} - -func (_c *AuthService_CreateAccessToken_Call) Return(token string, err error) *AuthService_CreateAccessToken_Call { - _c.Call.Return(token, err) - return _c -} - -func (_c *AuthService_CreateAccessToken_Call) RunAndReturn(run func(context.Context, uint32, string, time.Duration) (string, error)) *AuthService_CreateAccessToken_Call { - _c.Call.Return(run) - return _c -} - -// DeleteAccessToken provides a mock function with given fields: ctx, tokenID -func (_m *AuthService) DeleteAccessToken(ctx context.Context, tokenID uint32) (bool, error) { - ret := _m.Called(ctx, tokenID) +// GetByToken provides a mock function for the type AuthService +func (_mock *AuthService) GetByToken(ctx context.Context, token string) (auth.Auth, error) { + ret := _mock.Called(ctx, token) - var r0 bool - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (bool, error)); ok { - return rf(ctx, tokenID) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32) bool); ok { - r0 = rf(ctx, tokenID) - } else { - r0 = ret.Get(0).(bool) + if len(ret) == 0 { + panic("no return value specified for GetByToken") } - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, tokenID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// AuthService_DeleteAccessToken_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteAccessToken' -type AuthService_DeleteAccessToken_Call struct { - *mock.Call -} - -// DeleteAccessToken is a helper method to define mock.On call -// - ctx context.Context -// - tokenID uint32 -func (_e *AuthService_Expecter) DeleteAccessToken(ctx interface{}, tokenID interface{}) *AuthService_DeleteAccessToken_Call { - return &AuthService_DeleteAccessToken_Call{Call: _e.mock.On("DeleteAccessToken", ctx, tokenID)} -} - -func (_c *AuthService_DeleteAccessToken_Call) Run(run func(ctx context.Context, tokenID uint32)) *AuthService_DeleteAccessToken_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) - }) - return _c -} - -func (_c *AuthService_DeleteAccessToken_Call) Return(_a0 bool, _a1 error) *AuthService_DeleteAccessToken_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *AuthService_DeleteAccessToken_Call) RunAndReturn(run func(context.Context, uint32) (bool, error)) *AuthService_DeleteAccessToken_Call { - _c.Call.Return(run) - return _c -} - -// GetByID provides a mock function with given fields: ctx, userID -func (_m *AuthService) GetByID(ctx context.Context, userID uint32) (auth.Auth, error) { - ret := _m.Called(ctx, userID) - var r0 auth.Auth var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (auth.Auth, error)); ok { - return rf(ctx, userID) + if returnFunc, ok := ret.Get(0).(func(context.Context, string) (auth.Auth, error)); ok { + return returnFunc(ctx, token) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) auth.Auth); ok { - r0 = rf(ctx, userID) + if returnFunc, ok := ret.Get(0).(func(context.Context, string) auth.Auth); ok { + r0 = returnFunc(ctx, token) } else { r0 = ret.Get(0).(auth.Auth) } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, userID) + if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = returnFunc(ctx, token) } else { r1 = ret.Error(1) } - - return r0, r1 -} - -// AuthService_GetByID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByID' -type AuthService_GetByID_Call struct { - *mock.Call -} - -// GetByID is a helper method to define mock.On call -// - ctx context.Context -// - userID uint32 -func (_e *AuthService_Expecter) GetByID(ctx interface{}, userID interface{}) *AuthService_GetByID_Call { - return &AuthService_GetByID_Call{Call: _e.mock.On("GetByID", ctx, userID)} -} - -func (_c *AuthService_GetByID_Call) Run(run func(ctx context.Context, userID uint32)) *AuthService_GetByID_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) - }) - return _c -} - -func (_c *AuthService_GetByID_Call) Return(_a0 auth.Auth, _a1 error) *AuthService_GetByID_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *AuthService_GetByID_Call) RunAndReturn(run func(context.Context, uint32) (auth.Auth, error)) *AuthService_GetByID_Call { - _c.Call.Return(run) - return _c -} - -// GetByToken provides a mock function with given fields: ctx, token -func (_m *AuthService) GetByToken(ctx context.Context, token string) (auth.Auth, error) { - ret := _m.Called(ctx, token) - - var r0 auth.Auth - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (auth.Auth, error)); ok { - return rf(ctx, token) - } - if rf, ok := ret.Get(0).(func(context.Context, string) auth.Auth); ok { - r0 = rf(ctx, token) - } else { - r0 = ret.Get(0).(auth.Auth) - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, token) - } else { - r1 = ret.Error(1) - } - return r0, r1 } @@ -277,201 +78,28 @@ func (_e *AuthService_Expecter) GetByToken(ctx interface{}, token interface{}) * func (_c *AuthService_GetByToken_Call) Run(run func(ctx context.Context, token string)) *AuthService_GetByToken_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) - }) - return _c -} - -func (_c *AuthService_GetByToken_Call) Return(_a0 auth.Auth, _a1 error) *AuthService_GetByToken_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *AuthService_GetByToken_Call) RunAndReturn(run func(context.Context, string) (auth.Auth, error)) *AuthService_GetByToken_Call { - _c.Call.Return(run) - return _c -} - -// GetTokenByID provides a mock function with given fields: ctx, tokenID -func (_m *AuthService) GetTokenByID(ctx context.Context, tokenID uint32) (auth.AccessToken, error) { - ret := _m.Called(ctx, tokenID) - - var r0 auth.AccessToken - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (auth.AccessToken, error)); ok { - return rf(ctx, tokenID) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32) auth.AccessToken); ok { - r0 = rf(ctx, tokenID) - } else { - r0 = ret.Get(0).(auth.AccessToken) - } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, tokenID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// AuthService_GetTokenByID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTokenByID' -type AuthService_GetTokenByID_Call struct { - *mock.Call -} - -// GetTokenByID is a helper method to define mock.On call -// - ctx context.Context -// - tokenID uint32 -func (_e *AuthService_Expecter) GetTokenByID(ctx interface{}, tokenID interface{}) *AuthService_GetTokenByID_Call { - return &AuthService_GetTokenByID_Call{Call: _e.mock.On("GetTokenByID", ctx, tokenID)} -} - -func (_c *AuthService_GetTokenByID_Call) Run(run func(ctx context.Context, tokenID uint32)) *AuthService_GetTokenByID_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) - }) - return _c -} - -func (_c *AuthService_GetTokenByID_Call) Return(_a0 auth.AccessToken, _a1 error) *AuthService_GetTokenByID_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *AuthService_GetTokenByID_Call) RunAndReturn(run func(context.Context, uint32) (auth.AccessToken, error)) *AuthService_GetTokenByID_Call { - _c.Call.Return(run) - return _c -} - -// ListAccessToken provides a mock function with given fields: ctx, userID -func (_m *AuthService) ListAccessToken(ctx context.Context, userID uint32) ([]auth.AccessToken, error) { - ret := _m.Called(ctx, userID) - - var r0 []auth.AccessToken - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) ([]auth.AccessToken, error)); ok { - return rf(ctx, userID) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32) []auth.AccessToken); ok { - r0 = rf(ctx, userID) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]auth.AccessToken) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) } - } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, userID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// AuthService_ListAccessToken_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListAccessToken' -type AuthService_ListAccessToken_Call struct { - *mock.Call -} - -// ListAccessToken is a helper method to define mock.On call -// - ctx context.Context -// - userID uint32 -func (_e *AuthService_Expecter) ListAccessToken(ctx interface{}, userID interface{}) *AuthService_ListAccessToken_Call { - return &AuthService_ListAccessToken_Call{Call: _e.mock.On("ListAccessToken", ctx, userID)} -} - -func (_c *AuthService_ListAccessToken_Call) Run(run func(ctx context.Context, userID uint32)) *AuthService_ListAccessToken_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) - }) - return _c -} - -func (_c *AuthService_ListAccessToken_Call) Return(_a0 []auth.AccessToken, _a1 error) *AuthService_ListAccessToken_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *AuthService_ListAccessToken_Call) RunAndReturn(run func(context.Context, uint32) ([]auth.AccessToken, error)) *AuthService_ListAccessToken_Call { - _c.Call.Return(run) - return _c -} - -// Login provides a mock function with given fields: ctx, email, password -func (_m *AuthService) Login(ctx context.Context, email string, password string) (auth.Auth, bool, error) { - ret := _m.Called(ctx, email, password) - - var r0 auth.Auth - var r1 bool - var r2 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) (auth.Auth, bool, error)); ok { - return rf(ctx, email, password) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string) auth.Auth); ok { - r0 = rf(ctx, email, password) - } else { - r0 = ret.Get(0).(auth.Auth) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, string) bool); ok { - r1 = rf(ctx, email, password) - } else { - r1 = ret.Get(1).(bool) - } - - if rf, ok := ret.Get(2).(func(context.Context, string, string) error); ok { - r2 = rf(ctx, email, password) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// AuthService_Login_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Login' -type AuthService_Login_Call struct { - *mock.Call -} - -// Login is a helper method to define mock.On call -// - ctx context.Context -// - email string -// - password string -func (_e *AuthService_Expecter) Login(ctx interface{}, email interface{}, password interface{}) *AuthService_Login_Call { - return &AuthService_Login_Call{Call: _e.mock.On("Login", ctx, email, password)} -} - -func (_c *AuthService_Login_Call) Run(run func(ctx context.Context, email string, password string)) *AuthService_Login_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string)) + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *AuthService_Login_Call) Return(_a0 auth.Auth, _a1 bool, _a2 error) *AuthService_Login_Call { - _c.Call.Return(_a0, _a1, _a2) +func (_c *AuthService_GetByToken_Call) Return(auth1 auth.Auth, err error) *AuthService_GetByToken_Call { + _c.Call.Return(auth1, err) return _c } -func (_c *AuthService_Login_Call) RunAndReturn(run func(context.Context, string, string) (auth.Auth, bool, error)) *AuthService_Login_Call { +func (_c *AuthService_GetByToken_Call) RunAndReturn(run func(ctx context.Context, token string) (auth.Auth, error)) *AuthService_GetByToken_Call { _c.Call.Return(run) return _c } - -type mockConstructorTestingTNewAuthService interface { - mock.TestingT - Cleanup(func()) -} - -// NewAuthService creates a new instance of AuthService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewAuthService(t mockConstructorTestingTNewAuthService) *AuthService { - mock := &AuthService{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/mocks/CacheRedisCache.go b/internal/mocks/CacheRedisCache.go new file mode 100644 index 000000000..9531a5901 --- /dev/null +++ b/internal/mocks/CacheRedisCache.go @@ -0,0 +1,309 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package mocks + +import ( + "context" + "time" + + mock "github.com/stretchr/testify/mock" +) + +// NewRedisCache creates a new instance of RedisCache. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewRedisCache(t interface { + mock.TestingT + Cleanup(func()) +}) *RedisCache { + mock := &RedisCache{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// RedisCache is an autogenerated mock type for the RedisCache type +type RedisCache struct { + mock.Mock +} + +type RedisCache_Expecter struct { + mock *mock.Mock +} + +func (_m *RedisCache) EXPECT() *RedisCache_Expecter { + return &RedisCache_Expecter{mock: &_m.Mock} +} + +// Del provides a mock function for the type RedisCache +func (_mock *RedisCache) Del(ctx context.Context, keys ...string) error { + var tmpRet mock.Arguments + if len(keys) > 0 { + tmpRet = _mock.Called(ctx, keys) + } else { + tmpRet = _mock.Called(ctx) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for Del") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, ...string) error); ok { + r0 = returnFunc(ctx, keys...) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// RedisCache_Del_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Del' +type RedisCache_Del_Call struct { + *mock.Call +} + +// Del is a helper method to define mock.On call +// - ctx context.Context +// - keys ...string +func (_e *RedisCache_Expecter) Del(ctx interface{}, keys ...interface{}) *RedisCache_Del_Call { + return &RedisCache_Del_Call{Call: _e.mock.On("Del", + append([]interface{}{ctx}, keys...)...)} +} + +func (_c *RedisCache_Del_Call) Run(run func(ctx context.Context, keys ...string)) *RedisCache_Del_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []string + var variadicArgs []string + if len(args) > 1 { + variadicArgs = args[1].([]string) + } + arg1 = variadicArgs + run( + arg0, + arg1..., + ) + }) + return _c +} + +func (_c *RedisCache_Del_Call) Return(err error) *RedisCache_Del_Call { + _c.Call.Return(err) + return _c +} + +func (_c *RedisCache_Del_Call) RunAndReturn(run func(ctx context.Context, keys ...string) error) *RedisCache_Del_Call { + _c.Call.Return(run) + return _c +} + +// Get provides a mock function for the type RedisCache +func (_mock *RedisCache) Get(ctx context.Context, key string, value any) (bool, error) { + ret := _mock.Called(ctx, key, value) + + if len(ret) == 0 { + panic("no return value specified for Get") + } + + var r0 bool + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string, any) (bool, error)); ok { + return returnFunc(ctx, key, value) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string, any) bool); ok { + r0 = returnFunc(ctx, key, value) + } else { + r0 = ret.Get(0).(bool) + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string, any) error); ok { + r1 = returnFunc(ctx, key, value) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// RedisCache_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' +type RedisCache_Get_Call struct { + *mock.Call +} + +// Get is a helper method to define mock.On call +// - ctx context.Context +// - key string +// - value any +func (_e *RedisCache_Expecter) Get(ctx interface{}, key interface{}, value interface{}) *RedisCache_Get_Call { + return &RedisCache_Get_Call{Call: _e.mock.On("Get", ctx, key, value)} +} + +func (_c *RedisCache_Get_Call) Run(run func(ctx context.Context, key string, value any)) *RedisCache_Get_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 any + if args[2] != nil { + arg2 = args[2].(any) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *RedisCache_Get_Call) Return(b bool, err error) *RedisCache_Get_Call { + _c.Call.Return(b, err) + return _c +} + +func (_c *RedisCache_Get_Call) RunAndReturn(run func(ctx context.Context, key string, value any) (bool, error)) *RedisCache_Get_Call { + _c.Call.Return(run) + return _c +} + +// MGet provides a mock function for the type RedisCache +func (_mock *RedisCache) MGet(ctx context.Context, key []string, result any) error { + ret := _mock.Called(ctx, key, result) + + if len(ret) == 0 { + panic("no return value specified for MGet") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, []string, any) error); ok { + r0 = returnFunc(ctx, key, result) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// RedisCache_MGet_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MGet' +type RedisCache_MGet_Call struct { + *mock.Call +} + +// MGet is a helper method to define mock.On call +// - ctx context.Context +// - key []string +// - result any +func (_e *RedisCache_Expecter) MGet(ctx interface{}, key interface{}, result interface{}) *RedisCache_MGet_Call { + return &RedisCache_MGet_Call{Call: _e.mock.On("MGet", ctx, key, result)} +} + +func (_c *RedisCache_MGet_Call) Run(run func(ctx context.Context, key []string, result any)) *RedisCache_MGet_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []string + if args[1] != nil { + arg1 = args[1].([]string) + } + var arg2 any + if args[2] != nil { + arg2 = args[2].(any) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *RedisCache_MGet_Call) Return(err error) *RedisCache_MGet_Call { + _c.Call.Return(err) + return _c +} + +func (_c *RedisCache_MGet_Call) RunAndReturn(run func(ctx context.Context, key []string, result any) error) *RedisCache_MGet_Call { + _c.Call.Return(run) + return _c +} + +// Set provides a mock function for the type RedisCache +func (_mock *RedisCache) Set(ctx context.Context, key string, value any, ttl time.Duration) error { + ret := _mock.Called(ctx, key, value, ttl) + + if len(ret) == 0 { + panic("no return value specified for Set") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string, any, time.Duration) error); ok { + r0 = returnFunc(ctx, key, value, ttl) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// RedisCache_Set_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Set' +type RedisCache_Set_Call struct { + *mock.Call +} + +// Set is a helper method to define mock.On call +// - ctx context.Context +// - key string +// - value any +// - ttl time.Duration +func (_e *RedisCache_Expecter) Set(ctx interface{}, key interface{}, value interface{}, ttl interface{}) *RedisCache_Set_Call { + return &RedisCache_Set_Call{Call: _e.mock.On("Set", ctx, key, value, ttl)} +} + +func (_c *RedisCache_Set_Call) Run(run func(ctx context.Context, key string, value any, ttl time.Duration)) *RedisCache_Set_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 any + if args[2] != nil { + arg2 = args[2].(any) + } + var arg3 time.Duration + if args[3] != nil { + arg3 = args[3].(time.Duration) + } + run( + arg0, + arg1, + arg2, + arg3, + ) + }) + return _c +} + +func (_c *RedisCache_Set_Call) Return(err error) *RedisCache_Set_Call { + _c.Call.Return(err) + return _c +} + +func (_c *RedisCache_Set_Call) RunAndReturn(run func(ctx context.Context, key string, value any, ttl time.Duration) error) *RedisCache_Set_Call { + _c.Call.Return(run) + return _c +} diff --git a/internal/mocks/CharacterRepo.go b/internal/mocks/CharacterRepo.go index fadd66d79..9e6ddf007 100644 --- a/internal/mocks/CharacterRepo.go +++ b/internal/mocks/CharacterRepo.go @@ -1,18 +1,31 @@ -// Code generated by mockery v2.23.1. DO NOT EDIT. +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify package mocks import ( - context "context" + "context" - character "github.com/bangumi/server/internal/character" + "github.com/bangumi/server/domain" + "github.com/bangumi/server/internal/character" + "github.com/bangumi/server/internal/model" + mock "github.com/stretchr/testify/mock" +) - domain "github.com/bangumi/server/domain" +// NewCharacterRepo creates a new instance of CharacterRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewCharacterRepo(t interface { + mock.TestingT + Cleanup(func()) +}) *CharacterRepo { + mock := &CharacterRepo{} + mock.Mock.Test(t) - mock "github.com/stretchr/testify/mock" + t.Cleanup(func() { mock.AssertExpectations(t) }) - model "github.com/bangumi/server/internal/model" -) + return mock +} // CharacterRepo is an autogenerated mock type for the Repo type type CharacterRepo struct { @@ -27,27 +40,29 @@ func (_m *CharacterRepo) EXPECT() *CharacterRepo_Expecter { return &CharacterRepo_Expecter{mock: &_m.Mock} } -// Get provides a mock function with given fields: ctx, id -func (_m *CharacterRepo) Get(ctx context.Context, id uint32) (model.Character, error) { - ret := _m.Called(ctx, id) +// Get provides a mock function for the type CharacterRepo +func (_mock *CharacterRepo) Get(ctx context.Context, id model.CharacterID) (model.Character, error) { + ret := _mock.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for Get") + } var r0 model.Character var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (model.Character, error)); ok { - return rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.CharacterID) (model.Character, error)); ok { + return returnFunc(ctx, id) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) model.Character); ok { - r0 = rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.CharacterID) model.Character); ok { + r0 = returnFunc(ctx, id) } else { r0 = ret.Get(0).(model.Character) } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, id) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.CharacterID) error); ok { + r1 = returnFunc(ctx, id) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -58,51 +73,64 @@ type CharacterRepo_Get_Call struct { // Get is a helper method to define mock.On call // - ctx context.Context -// - id uint32 +// - id model.CharacterID func (_e *CharacterRepo_Expecter) Get(ctx interface{}, id interface{}) *CharacterRepo_Get_Call { return &CharacterRepo_Get_Call{Call: _e.mock.On("Get", ctx, id)} } -func (_c *CharacterRepo_Get_Call) Run(run func(ctx context.Context, id uint32)) *CharacterRepo_Get_Call { +func (_c *CharacterRepo_Get_Call) Run(run func(ctx context.Context, id model.CharacterID)) *CharacterRepo_Get_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.CharacterID + if args[1] != nil { + arg1 = args[1].(model.CharacterID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *CharacterRepo_Get_Call) Return(_a0 model.Character, _a1 error) *CharacterRepo_Get_Call { - _c.Call.Return(_a0, _a1) +func (_c *CharacterRepo_Get_Call) Return(character model.Character, err error) *CharacterRepo_Get_Call { + _c.Call.Return(character, err) return _c } -func (_c *CharacterRepo_Get_Call) RunAndReturn(run func(context.Context, uint32) (model.Character, error)) *CharacterRepo_Get_Call { +func (_c *CharacterRepo_Get_Call) RunAndReturn(run func(ctx context.Context, id model.CharacterID) (model.Character, error)) *CharacterRepo_Get_Call { _c.Call.Return(run) return _c } -// GetByIDs provides a mock function with given fields: ctx, ids -func (_m *CharacterRepo) GetByIDs(ctx context.Context, ids []uint32) (map[uint32]model.Character, error) { - ret := _m.Called(ctx, ids) +// GetByIDs provides a mock function for the type CharacterRepo +func (_mock *CharacterRepo) GetByIDs(ctx context.Context, ids []model.CharacterID) (map[model.CharacterID]model.Character, error) { + ret := _mock.Called(ctx, ids) - var r0 map[uint32]model.Character + if len(ret) == 0 { + panic("no return value specified for GetByIDs") + } + + var r0 map[model.CharacterID]model.Character var r1 error - if rf, ok := ret.Get(0).(func(context.Context, []uint32) (map[uint32]model.Character, error)); ok { - return rf(ctx, ids) + if returnFunc, ok := ret.Get(0).(func(context.Context, []model.CharacterID) (map[model.CharacterID]model.Character, error)); ok { + return returnFunc(ctx, ids) } - if rf, ok := ret.Get(0).(func(context.Context, []uint32) map[uint32]model.Character); ok { - r0 = rf(ctx, ids) + if returnFunc, ok := ret.Get(0).(func(context.Context, []model.CharacterID) map[model.CharacterID]model.Character); ok { + r0 = returnFunc(ctx, ids) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(map[uint32]model.Character) + r0 = ret.Get(0).(map[model.CharacterID]model.Character) } } - - if rf, ok := ret.Get(1).(func(context.Context, []uint32) error); ok { - r1 = rf(ctx, ids) + if returnFunc, ok := ret.Get(1).(func(context.Context, []model.CharacterID) error); ok { + r1 = returnFunc(ctx, ids) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -113,51 +141,64 @@ type CharacterRepo_GetByIDs_Call struct { // GetByIDs is a helper method to define mock.On call // - ctx context.Context -// - ids []uint32 +// - ids []model.CharacterID func (_e *CharacterRepo_Expecter) GetByIDs(ctx interface{}, ids interface{}) *CharacterRepo_GetByIDs_Call { return &CharacterRepo_GetByIDs_Call{Call: _e.mock.On("GetByIDs", ctx, ids)} } -func (_c *CharacterRepo_GetByIDs_Call) Run(run func(ctx context.Context, ids []uint32)) *CharacterRepo_GetByIDs_Call { +func (_c *CharacterRepo_GetByIDs_Call) Run(run func(ctx context.Context, ids []model.CharacterID)) *CharacterRepo_GetByIDs_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].([]uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []model.CharacterID + if args[1] != nil { + arg1 = args[1].([]model.CharacterID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *CharacterRepo_GetByIDs_Call) Return(_a0 map[uint32]model.Character, _a1 error) *CharacterRepo_GetByIDs_Call { - _c.Call.Return(_a0, _a1) +func (_c *CharacterRepo_GetByIDs_Call) Return(vToCharacter map[model.CharacterID]model.Character, err error) *CharacterRepo_GetByIDs_Call { + _c.Call.Return(vToCharacter, err) return _c } -func (_c *CharacterRepo_GetByIDs_Call) RunAndReturn(run func(context.Context, []uint32) (map[uint32]model.Character, error)) *CharacterRepo_GetByIDs_Call { +func (_c *CharacterRepo_GetByIDs_Call) RunAndReturn(run func(ctx context.Context, ids []model.CharacterID) (map[model.CharacterID]model.Character, error)) *CharacterRepo_GetByIDs_Call { _c.Call.Return(run) return _c } -// GetPersonRelated provides a mock function with given fields: ctx, personID -func (_m *CharacterRepo) GetPersonRelated(ctx context.Context, personID uint32) ([]domain.PersonCharacterRelation, error) { - ret := _m.Called(ctx, personID) +// GetPersonRelated provides a mock function for the type CharacterRepo +func (_mock *CharacterRepo) GetPersonRelated(ctx context.Context, personID model.PersonID) ([]domain.PersonCharacterRelation, error) { + ret := _mock.Called(ctx, personID) + + if len(ret) == 0 { + panic("no return value specified for GetPersonRelated") + } var r0 []domain.PersonCharacterRelation var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) ([]domain.PersonCharacterRelation, error)); ok { - return rf(ctx, personID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.PersonID) ([]domain.PersonCharacterRelation, error)); ok { + return returnFunc(ctx, personID) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) []domain.PersonCharacterRelation); ok { - r0 = rf(ctx, personID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.PersonID) []domain.PersonCharacterRelation); ok { + r0 = returnFunc(ctx, personID) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]domain.PersonCharacterRelation) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, personID) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.PersonID) error); ok { + r1 = returnFunc(ctx, personID) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -168,51 +209,64 @@ type CharacterRepo_GetPersonRelated_Call struct { // GetPersonRelated is a helper method to define mock.On call // - ctx context.Context -// - personID uint32 +// - personID model.PersonID func (_e *CharacterRepo_Expecter) GetPersonRelated(ctx interface{}, personID interface{}) *CharacterRepo_GetPersonRelated_Call { return &CharacterRepo_GetPersonRelated_Call{Call: _e.mock.On("GetPersonRelated", ctx, personID)} } -func (_c *CharacterRepo_GetPersonRelated_Call) Run(run func(ctx context.Context, personID uint32)) *CharacterRepo_GetPersonRelated_Call { +func (_c *CharacterRepo_GetPersonRelated_Call) Run(run func(ctx context.Context, personID model.PersonID)) *CharacterRepo_GetPersonRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.PersonID + if args[1] != nil { + arg1 = args[1].(model.PersonID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *CharacterRepo_GetPersonRelated_Call) Return(_a0 []domain.PersonCharacterRelation, _a1 error) *CharacterRepo_GetPersonRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *CharacterRepo_GetPersonRelated_Call) Return(personCharacterRelations []domain.PersonCharacterRelation, err error) *CharacterRepo_GetPersonRelated_Call { + _c.Call.Return(personCharacterRelations, err) return _c } -func (_c *CharacterRepo_GetPersonRelated_Call) RunAndReturn(run func(context.Context, uint32) ([]domain.PersonCharacterRelation, error)) *CharacterRepo_GetPersonRelated_Call { +func (_c *CharacterRepo_GetPersonRelated_Call) RunAndReturn(run func(ctx context.Context, personID model.PersonID) ([]domain.PersonCharacterRelation, error)) *CharacterRepo_GetPersonRelated_Call { _c.Call.Return(run) return _c } -// GetSubjectRelated provides a mock function with given fields: ctx, subjectID -func (_m *CharacterRepo) GetSubjectRelated(ctx context.Context, subjectID uint32) ([]domain.SubjectCharacterRelation, error) { - ret := _m.Called(ctx, subjectID) +// GetSubjectRelated provides a mock function for the type CharacterRepo +func (_mock *CharacterRepo) GetSubjectRelated(ctx context.Context, subjectID model.SubjectID) ([]domain.SubjectCharacterRelation, error) { + ret := _mock.Called(ctx, subjectID) + + if len(ret) == 0 { + panic("no return value specified for GetSubjectRelated") + } var r0 []domain.SubjectCharacterRelation var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) ([]domain.SubjectCharacterRelation, error)); ok { - return rf(ctx, subjectID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID) ([]domain.SubjectCharacterRelation, error)); ok { + return returnFunc(ctx, subjectID) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) []domain.SubjectCharacterRelation); ok { - r0 = rf(ctx, subjectID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID) []domain.SubjectCharacterRelation); ok { + r0 = returnFunc(ctx, subjectID) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]domain.SubjectCharacterRelation) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, subjectID) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.SubjectID) error); ok { + r1 = returnFunc(ctx, subjectID) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -223,51 +277,64 @@ type CharacterRepo_GetSubjectRelated_Call struct { // GetSubjectRelated is a helper method to define mock.On call // - ctx context.Context -// - subjectID uint32 +// - subjectID model.SubjectID func (_e *CharacterRepo_Expecter) GetSubjectRelated(ctx interface{}, subjectID interface{}) *CharacterRepo_GetSubjectRelated_Call { return &CharacterRepo_GetSubjectRelated_Call{Call: _e.mock.On("GetSubjectRelated", ctx, subjectID)} } -func (_c *CharacterRepo_GetSubjectRelated_Call) Run(run func(ctx context.Context, subjectID uint32)) *CharacterRepo_GetSubjectRelated_Call { +func (_c *CharacterRepo_GetSubjectRelated_Call) Run(run func(ctx context.Context, subjectID model.SubjectID)) *CharacterRepo_GetSubjectRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.SubjectID + if args[1] != nil { + arg1 = args[1].(model.SubjectID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *CharacterRepo_GetSubjectRelated_Call) Return(_a0 []domain.SubjectCharacterRelation, _a1 error) *CharacterRepo_GetSubjectRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *CharacterRepo_GetSubjectRelated_Call) Return(subjectCharacterRelations []domain.SubjectCharacterRelation, err error) *CharacterRepo_GetSubjectRelated_Call { + _c.Call.Return(subjectCharacterRelations, err) return _c } -func (_c *CharacterRepo_GetSubjectRelated_Call) RunAndReturn(run func(context.Context, uint32) ([]domain.SubjectCharacterRelation, error)) *CharacterRepo_GetSubjectRelated_Call { +func (_c *CharacterRepo_GetSubjectRelated_Call) RunAndReturn(run func(ctx context.Context, subjectID model.SubjectID) ([]domain.SubjectCharacterRelation, error)) *CharacterRepo_GetSubjectRelated_Call { _c.Call.Return(run) return _c } -// GetSubjectRelationByIDs provides a mock function with given fields: ctx, ids -func (_m *CharacterRepo) GetSubjectRelationByIDs(ctx context.Context, ids []character.SubjectCompositeID) ([]domain.SubjectCharacterRelation, error) { - ret := _m.Called(ctx, ids) +// GetSubjectRelationByIDs provides a mock function for the type CharacterRepo +func (_mock *CharacterRepo) GetSubjectRelationByIDs(ctx context.Context, ids []character.SubjectCompositeID) ([]domain.SubjectCharacterRelation, error) { + ret := _mock.Called(ctx, ids) + + if len(ret) == 0 { + panic("no return value specified for GetSubjectRelationByIDs") + } var r0 []domain.SubjectCharacterRelation var r1 error - if rf, ok := ret.Get(0).(func(context.Context, []character.SubjectCompositeID) ([]domain.SubjectCharacterRelation, error)); ok { - return rf(ctx, ids) + if returnFunc, ok := ret.Get(0).(func(context.Context, []character.SubjectCompositeID) ([]domain.SubjectCharacterRelation, error)); ok { + return returnFunc(ctx, ids) } - if rf, ok := ret.Get(0).(func(context.Context, []character.SubjectCompositeID) []domain.SubjectCharacterRelation); ok { - r0 = rf(ctx, ids) + if returnFunc, ok := ret.Get(0).(func(context.Context, []character.SubjectCompositeID) []domain.SubjectCharacterRelation); ok { + r0 = returnFunc(ctx, ids) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]domain.SubjectCharacterRelation) } } - - if rf, ok := ret.Get(1).(func(context.Context, []character.SubjectCompositeID) error); ok { - r1 = rf(ctx, ids) + if returnFunc, ok := ret.Get(1).(func(context.Context, []character.SubjectCompositeID) error); ok { + r1 = returnFunc(ctx, ids) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -285,32 +352,28 @@ func (_e *CharacterRepo_Expecter) GetSubjectRelationByIDs(ctx interface{}, ids i func (_c *CharacterRepo_GetSubjectRelationByIDs_Call) Run(run func(ctx context.Context, ids []character.SubjectCompositeID)) *CharacterRepo_GetSubjectRelationByIDs_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].([]character.SubjectCompositeID)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []character.SubjectCompositeID + if args[1] != nil { + arg1 = args[1].([]character.SubjectCompositeID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *CharacterRepo_GetSubjectRelationByIDs_Call) Return(_a0 []domain.SubjectCharacterRelation, _a1 error) *CharacterRepo_GetSubjectRelationByIDs_Call { - _c.Call.Return(_a0, _a1) +func (_c *CharacterRepo_GetSubjectRelationByIDs_Call) Return(subjectCharacterRelations []domain.SubjectCharacterRelation, err error) *CharacterRepo_GetSubjectRelationByIDs_Call { + _c.Call.Return(subjectCharacterRelations, err) return _c } -func (_c *CharacterRepo_GetSubjectRelationByIDs_Call) RunAndReturn(run func(context.Context, []character.SubjectCompositeID) ([]domain.SubjectCharacterRelation, error)) *CharacterRepo_GetSubjectRelationByIDs_Call { +func (_c *CharacterRepo_GetSubjectRelationByIDs_Call) RunAndReturn(run func(ctx context.Context, ids []character.SubjectCompositeID) ([]domain.SubjectCharacterRelation, error)) *CharacterRepo_GetSubjectRelationByIDs_Call { _c.Call.Return(run) return _c } - -type mockConstructorTestingTNewCharacterRepo interface { - mock.TestingT - Cleanup(func()) -} - -// NewCharacterRepo creates a new instance of CharacterRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewCharacterRepo(t mockConstructorTestingTNewCharacterRepo) *CharacterRepo { - mock := &CharacterRepo{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/mocks/CollectionRepo.go b/internal/mocks/CollectionRepo.go deleted file mode 100644 index 50862d713..000000000 --- a/internal/mocks/CollectionRepo.go +++ /dev/null @@ -1,420 +0,0 @@ -// Code generated by mockery v2.23.1. DO NOT EDIT. - -package mocks - -import ( - collections "github.com/bangumi/server/internal/collections" - collection "github.com/bangumi/server/internal/collections/domain/collection" - - context "context" - - mock "github.com/stretchr/testify/mock" - - query "github.com/bangumi/server/dal/query" - - time "time" -) - -// CollectionRepo is an autogenerated mock type for the Repo type -type CollectionRepo struct { - mock.Mock -} - -type CollectionRepo_Expecter struct { - mock *mock.Mock -} - -func (_m *CollectionRepo) EXPECT() *CollectionRepo_Expecter { - return &CollectionRepo_Expecter{mock: &_m.Mock} -} - -// CountSubjectCollections provides a mock function with given fields: ctx, userID, subjectType, collectionType, showPrivate -func (_m *CollectionRepo) CountSubjectCollections(ctx context.Context, userID uint32, subjectType uint8, collectionType collection.SubjectCollection, showPrivate bool) (int64, error) { - ret := _m.Called(ctx, userID, subjectType, collectionType, showPrivate) - - var r0 int64 - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint8, collection.SubjectCollection, bool) (int64, error)); ok { - return rf(ctx, userID, subjectType, collectionType, showPrivate) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint8, collection.SubjectCollection, bool) int64); ok { - r0 = rf(ctx, userID, subjectType, collectionType, showPrivate) - } else { - r0 = ret.Get(0).(int64) - } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, uint8, collection.SubjectCollection, bool) error); ok { - r1 = rf(ctx, userID, subjectType, collectionType, showPrivate) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// CollectionRepo_CountSubjectCollections_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CountSubjectCollections' -type CollectionRepo_CountSubjectCollections_Call struct { - *mock.Call -} - -// CountSubjectCollections is a helper method to define mock.On call -// - ctx context.Context -// - userID uint32 -// - subjectType uint8 -// - collectionType collection.SubjectCollection -// - showPrivate bool -func (_e *CollectionRepo_Expecter) CountSubjectCollections(ctx interface{}, userID interface{}, subjectType interface{}, collectionType interface{}, showPrivate interface{}) *CollectionRepo_CountSubjectCollections_Call { - return &CollectionRepo_CountSubjectCollections_Call{Call: _e.mock.On("CountSubjectCollections", ctx, userID, subjectType, collectionType, showPrivate)} -} - -func (_c *CollectionRepo_CountSubjectCollections_Call) Run(run func(ctx context.Context, userID uint32, subjectType uint8, collectionType collection.SubjectCollection, showPrivate bool)) *CollectionRepo_CountSubjectCollections_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(uint8), args[3].(collection.SubjectCollection), args[4].(bool)) - }) - return _c -} - -func (_c *CollectionRepo_CountSubjectCollections_Call) Return(_a0 int64, _a1 error) *CollectionRepo_CountSubjectCollections_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *CollectionRepo_CountSubjectCollections_Call) RunAndReturn(run func(context.Context, uint32, uint8, collection.SubjectCollection, bool) (int64, error)) *CollectionRepo_CountSubjectCollections_Call { - _c.Call.Return(run) - return _c -} - -// GetSubjectCollection provides a mock function with given fields: ctx, userID, subjectID -func (_m *CollectionRepo) GetSubjectCollection(ctx context.Context, userID uint32, subjectID uint32) (collection.UserSubjectCollection, error) { - ret := _m.Called(ctx, userID, subjectID) - - var r0 collection.UserSubjectCollection - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint32) (collection.UserSubjectCollection, error)); ok { - return rf(ctx, userID, subjectID) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint32) collection.UserSubjectCollection); ok { - r0 = rf(ctx, userID, subjectID) - } else { - r0 = ret.Get(0).(collection.UserSubjectCollection) - } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, uint32) error); ok { - r1 = rf(ctx, userID, subjectID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// CollectionRepo_GetSubjectCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSubjectCollection' -type CollectionRepo_GetSubjectCollection_Call struct { - *mock.Call -} - -// GetSubjectCollection is a helper method to define mock.On call -// - ctx context.Context -// - userID uint32 -// - subjectID uint32 -func (_e *CollectionRepo_Expecter) GetSubjectCollection(ctx interface{}, userID interface{}, subjectID interface{}) *CollectionRepo_GetSubjectCollection_Call { - return &CollectionRepo_GetSubjectCollection_Call{Call: _e.mock.On("GetSubjectCollection", ctx, userID, subjectID)} -} - -func (_c *CollectionRepo_GetSubjectCollection_Call) Run(run func(ctx context.Context, userID uint32, subjectID uint32)) *CollectionRepo_GetSubjectCollection_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(uint32)) - }) - return _c -} - -func (_c *CollectionRepo_GetSubjectCollection_Call) Return(_a0 collection.UserSubjectCollection, _a1 error) *CollectionRepo_GetSubjectCollection_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *CollectionRepo_GetSubjectCollection_Call) RunAndReturn(run func(context.Context, uint32, uint32) (collection.UserSubjectCollection, error)) *CollectionRepo_GetSubjectCollection_Call { - _c.Call.Return(run) - return _c -} - -// GetSubjectEpisodesCollection provides a mock function with given fields: ctx, userID, subjectID -func (_m *CollectionRepo) GetSubjectEpisodesCollection(ctx context.Context, userID uint32, subjectID uint32) (collection.UserSubjectEpisodesCollection, error) { - ret := _m.Called(ctx, userID, subjectID) - - var r0 collection.UserSubjectEpisodesCollection - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint32) (collection.UserSubjectEpisodesCollection, error)); ok { - return rf(ctx, userID, subjectID) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint32) collection.UserSubjectEpisodesCollection); ok { - r0 = rf(ctx, userID, subjectID) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(collection.UserSubjectEpisodesCollection) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, uint32) error); ok { - r1 = rf(ctx, userID, subjectID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// CollectionRepo_GetSubjectEpisodesCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSubjectEpisodesCollection' -type CollectionRepo_GetSubjectEpisodesCollection_Call struct { - *mock.Call -} - -// GetSubjectEpisodesCollection is a helper method to define mock.On call -// - ctx context.Context -// - userID uint32 -// - subjectID uint32 -func (_e *CollectionRepo_Expecter) GetSubjectEpisodesCollection(ctx interface{}, userID interface{}, subjectID interface{}) *CollectionRepo_GetSubjectEpisodesCollection_Call { - return &CollectionRepo_GetSubjectEpisodesCollection_Call{Call: _e.mock.On("GetSubjectEpisodesCollection", ctx, userID, subjectID)} -} - -func (_c *CollectionRepo_GetSubjectEpisodesCollection_Call) Run(run func(ctx context.Context, userID uint32, subjectID uint32)) *CollectionRepo_GetSubjectEpisodesCollection_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(uint32)) - }) - return _c -} - -func (_c *CollectionRepo_GetSubjectEpisodesCollection_Call) Return(_a0 collection.UserSubjectEpisodesCollection, _a1 error) *CollectionRepo_GetSubjectEpisodesCollection_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *CollectionRepo_GetSubjectEpisodesCollection_Call) RunAndReturn(run func(context.Context, uint32, uint32) (collection.UserSubjectEpisodesCollection, error)) *CollectionRepo_GetSubjectEpisodesCollection_Call { - _c.Call.Return(run) - return _c -} - -// ListSubjectCollection provides a mock function with given fields: ctx, userID, subjectType, collectionType, showPrivate, limit, offset -func (_m *CollectionRepo) ListSubjectCollection(ctx context.Context, userID uint32, subjectType uint8, collectionType collection.SubjectCollection, showPrivate bool, limit int, offset int) ([]collection.UserSubjectCollection, error) { - ret := _m.Called(ctx, userID, subjectType, collectionType, showPrivate, limit, offset) - - var r0 []collection.UserSubjectCollection - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint8, collection.SubjectCollection, bool, int, int) ([]collection.UserSubjectCollection, error)); ok { - return rf(ctx, userID, subjectType, collectionType, showPrivate, limit, offset) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint8, collection.SubjectCollection, bool, int, int) []collection.UserSubjectCollection); ok { - r0 = rf(ctx, userID, subjectType, collectionType, showPrivate, limit, offset) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]collection.UserSubjectCollection) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, uint8, collection.SubjectCollection, bool, int, int) error); ok { - r1 = rf(ctx, userID, subjectType, collectionType, showPrivate, limit, offset) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// CollectionRepo_ListSubjectCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListSubjectCollection' -type CollectionRepo_ListSubjectCollection_Call struct { - *mock.Call -} - -// ListSubjectCollection is a helper method to define mock.On call -// - ctx context.Context -// - userID uint32 -// - subjectType uint8 -// - collectionType collection.SubjectCollection -// - showPrivate bool -// - limit int -// - offset int -func (_e *CollectionRepo_Expecter) ListSubjectCollection(ctx interface{}, userID interface{}, subjectType interface{}, collectionType interface{}, showPrivate interface{}, limit interface{}, offset interface{}) *CollectionRepo_ListSubjectCollection_Call { - return &CollectionRepo_ListSubjectCollection_Call{Call: _e.mock.On("ListSubjectCollection", ctx, userID, subjectType, collectionType, showPrivate, limit, offset)} -} - -func (_c *CollectionRepo_ListSubjectCollection_Call) Run(run func(ctx context.Context, userID uint32, subjectType uint8, collectionType collection.SubjectCollection, showPrivate bool, limit int, offset int)) *CollectionRepo_ListSubjectCollection_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(uint8), args[3].(collection.SubjectCollection), args[4].(bool), args[5].(int), args[6].(int)) - }) - return _c -} - -func (_c *CollectionRepo_ListSubjectCollection_Call) Return(_a0 []collection.UserSubjectCollection, _a1 error) *CollectionRepo_ListSubjectCollection_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *CollectionRepo_ListSubjectCollection_Call) RunAndReturn(run func(context.Context, uint32, uint8, collection.SubjectCollection, bool, int, int) ([]collection.UserSubjectCollection, error)) *CollectionRepo_ListSubjectCollection_Call { - _c.Call.Return(run) - return _c -} - -// UpdateEpisodeCollection provides a mock function with given fields: ctx, userID, subjectID, episodeIDs, _a4, at -func (_m *CollectionRepo) UpdateEpisodeCollection(ctx context.Context, userID uint32, subjectID uint32, episodeIDs []uint32, _a4 collection.EpisodeCollection, at time.Time) (collection.UserSubjectEpisodesCollection, error) { - ret := _m.Called(ctx, userID, subjectID, episodeIDs, _a4, at) - - var r0 collection.UserSubjectEpisodesCollection - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint32, []uint32, collection.EpisodeCollection, time.Time) (collection.UserSubjectEpisodesCollection, error)); ok { - return rf(ctx, userID, subjectID, episodeIDs, _a4, at) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint32, []uint32, collection.EpisodeCollection, time.Time) collection.UserSubjectEpisodesCollection); ok { - r0 = rf(ctx, userID, subjectID, episodeIDs, _a4, at) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(collection.UserSubjectEpisodesCollection) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, uint32, []uint32, collection.EpisodeCollection, time.Time) error); ok { - r1 = rf(ctx, userID, subjectID, episodeIDs, _a4, at) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// CollectionRepo_UpdateEpisodeCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateEpisodeCollection' -type CollectionRepo_UpdateEpisodeCollection_Call struct { - *mock.Call -} - -// UpdateEpisodeCollection is a helper method to define mock.On call -// - ctx context.Context -// - userID uint32 -// - subjectID uint32 -// - episodeIDs []uint32 -// - _a4 collection.EpisodeCollection -// - at time.Time -func (_e *CollectionRepo_Expecter) UpdateEpisodeCollection(ctx interface{}, userID interface{}, subjectID interface{}, episodeIDs interface{}, _a4 interface{}, at interface{}) *CollectionRepo_UpdateEpisodeCollection_Call { - return &CollectionRepo_UpdateEpisodeCollection_Call{Call: _e.mock.On("UpdateEpisodeCollection", ctx, userID, subjectID, episodeIDs, _a4, at)} -} - -func (_c *CollectionRepo_UpdateEpisodeCollection_Call) Run(run func(ctx context.Context, userID uint32, subjectID uint32, episodeIDs []uint32, _a4 collection.EpisodeCollection, at time.Time)) *CollectionRepo_UpdateEpisodeCollection_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(uint32), args[3].([]uint32), args[4].(collection.EpisodeCollection), args[5].(time.Time)) - }) - return _c -} - -func (_c *CollectionRepo_UpdateEpisodeCollection_Call) Return(_a0 collection.UserSubjectEpisodesCollection, _a1 error) *CollectionRepo_UpdateEpisodeCollection_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *CollectionRepo_UpdateEpisodeCollection_Call) RunAndReturn(run func(context.Context, uint32, uint32, []uint32, collection.EpisodeCollection, time.Time) (collection.UserSubjectEpisodesCollection, error)) *CollectionRepo_UpdateEpisodeCollection_Call { - _c.Call.Return(run) - return _c -} - -// UpdateSubjectCollection provides a mock function with given fields: ctx, userID, subjectID, at, ip, update -func (_m *CollectionRepo) UpdateSubjectCollection(ctx context.Context, userID uint32, subjectID uint32, at time.Time, ip string, update func(context.Context, *collection.Subject) (*collection.Subject, error)) error { - ret := _m.Called(ctx, userID, subjectID, at, ip, update) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint32, time.Time, string, func(context.Context, *collection.Subject) (*collection.Subject, error)) error); ok { - r0 = rf(ctx, userID, subjectID, at, ip, update) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// CollectionRepo_UpdateSubjectCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateSubjectCollection' -type CollectionRepo_UpdateSubjectCollection_Call struct { - *mock.Call -} - -// UpdateSubjectCollection is a helper method to define mock.On call -// - ctx context.Context -// - userID uint32 -// - subjectID uint32 -// - at time.Time -// - ip string -// - update func(context.Context , *collection.Subject)(*collection.Subject , error) -func (_e *CollectionRepo_Expecter) UpdateSubjectCollection(ctx interface{}, userID interface{}, subjectID interface{}, at interface{}, ip interface{}, update interface{}) *CollectionRepo_UpdateSubjectCollection_Call { - return &CollectionRepo_UpdateSubjectCollection_Call{Call: _e.mock.On("UpdateSubjectCollection", ctx, userID, subjectID, at, ip, update)} -} - -func (_c *CollectionRepo_UpdateSubjectCollection_Call) Run(run func(ctx context.Context, userID uint32, subjectID uint32, at time.Time, ip string, update func(context.Context, *collection.Subject) (*collection.Subject, error))) *CollectionRepo_UpdateSubjectCollection_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(uint32), args[3].(time.Time), args[4].(string), args[5].(func(context.Context, *collection.Subject) (*collection.Subject, error))) - }) - return _c -} - -func (_c *CollectionRepo_UpdateSubjectCollection_Call) Return(_a0 error) *CollectionRepo_UpdateSubjectCollection_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *CollectionRepo_UpdateSubjectCollection_Call) RunAndReturn(run func(context.Context, uint32, uint32, time.Time, string, func(context.Context, *collection.Subject) (*collection.Subject, error)) error) *CollectionRepo_UpdateSubjectCollection_Call { - _c.Call.Return(run) - return _c -} - -// WithQuery provides a mock function with given fields: _a0 -func (_m *CollectionRepo) WithQuery(_a0 *query.Query) collections.Repo { - ret := _m.Called(_a0) - - var r0 collections.Repo - if rf, ok := ret.Get(0).(func(*query.Query) collections.Repo); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(collections.Repo) - } - } - - return r0 -} - -// CollectionRepo_WithQuery_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WithQuery' -type CollectionRepo_WithQuery_Call struct { - *mock.Call -} - -// WithQuery is a helper method to define mock.On call -// - _a0 *query.Query -func (_e *CollectionRepo_Expecter) WithQuery(_a0 interface{}) *CollectionRepo_WithQuery_Call { - return &CollectionRepo_WithQuery_Call{Call: _e.mock.On("WithQuery", _a0)} -} - -func (_c *CollectionRepo_WithQuery_Call) Run(run func(_a0 *query.Query)) *CollectionRepo_WithQuery_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*query.Query)) - }) - return _c -} - -func (_c *CollectionRepo_WithQuery_Call) Return(_a0 collections.Repo) *CollectionRepo_WithQuery_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *CollectionRepo_WithQuery_Call) RunAndReturn(run func(*query.Query) collections.Repo) *CollectionRepo_WithQuery_Call { - _c.Call.Return(run) - return _c -} - -type mockConstructorTestingTNewCollectionRepo interface { - mock.TestingT - Cleanup(func()) -} - -// NewCollectionRepo creates a new instance of CollectionRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewCollectionRepo(t mockConstructorTestingTNewCollectionRepo) *CollectionRepo { - mock := &CollectionRepo{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/mocks/CollectionsRepo.go b/internal/mocks/CollectionsRepo.go new file mode 100644 index 000000000..4ba1c3d53 --- /dev/null +++ b/internal/mocks/CollectionsRepo.go @@ -0,0 +1,1052 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package mocks + +import ( + "context" + "time" + + "github.com/bangumi/server/dal/query" + "github.com/bangumi/server/internal/collections" + "github.com/bangumi/server/internal/collections/domain/collection" + "github.com/bangumi/server/internal/model" + mock "github.com/stretchr/testify/mock" +) + +// NewCollectionsRepo creates a new instance of CollectionsRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewCollectionsRepo(t interface { + mock.TestingT + Cleanup(func()) +}) *CollectionsRepo { + mock := &CollectionsRepo{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// CollectionsRepo is an autogenerated mock type for the Repo type +type CollectionsRepo struct { + mock.Mock +} + +type CollectionsRepo_Expecter struct { + mock *mock.Mock +} + +func (_m *CollectionsRepo) EXPECT() *CollectionsRepo_Expecter { + return &CollectionsRepo_Expecter{mock: &_m.Mock} +} + +// AddPersonCollection provides a mock function for the type CollectionsRepo +func (_mock *CollectionsRepo) AddPersonCollection(ctx context.Context, userID model.UserID, cat collection.PersonCollectCategory, targetID model.PersonID) error { + ret := _mock.Called(ctx, userID, cat, targetID) + + if len(ret) == 0 { + panic("no return value specified for AddPersonCollection") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, collection.PersonCollectCategory, model.PersonID) error); ok { + r0 = returnFunc(ctx, userID, cat, targetID) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// CollectionsRepo_AddPersonCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddPersonCollection' +type CollectionsRepo_AddPersonCollection_Call struct { + *mock.Call +} + +// AddPersonCollection is a helper method to define mock.On call +// - ctx context.Context +// - userID model.UserID +// - cat collection.PersonCollectCategory +// - targetID model.PersonID +func (_e *CollectionsRepo_Expecter) AddPersonCollection(ctx interface{}, userID interface{}, cat interface{}, targetID interface{}) *CollectionsRepo_AddPersonCollection_Call { + return &CollectionsRepo_AddPersonCollection_Call{Call: _e.mock.On("AddPersonCollection", ctx, userID, cat, targetID)} +} + +func (_c *CollectionsRepo_AddPersonCollection_Call) Run(run func(ctx context.Context, userID model.UserID, cat collection.PersonCollectCategory, targetID model.PersonID)) *CollectionsRepo_AddPersonCollection_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.UserID + if args[1] != nil { + arg1 = args[1].(model.UserID) + } + var arg2 collection.PersonCollectCategory + if args[2] != nil { + arg2 = args[2].(collection.PersonCollectCategory) + } + var arg3 model.PersonID + if args[3] != nil { + arg3 = args[3].(model.PersonID) + } + run( + arg0, + arg1, + arg2, + arg3, + ) + }) + return _c +} + +func (_c *CollectionsRepo_AddPersonCollection_Call) Return(err error) *CollectionsRepo_AddPersonCollection_Call { + _c.Call.Return(err) + return _c +} + +func (_c *CollectionsRepo_AddPersonCollection_Call) RunAndReturn(run func(ctx context.Context, userID model.UserID, cat collection.PersonCollectCategory, targetID model.PersonID) error) *CollectionsRepo_AddPersonCollection_Call { + _c.Call.Return(run) + return _c +} + +// CountPersonCollections provides a mock function for the type CollectionsRepo +func (_mock *CollectionsRepo) CountPersonCollections(ctx context.Context, userID model.UserID, cat collection.PersonCollectCategory) (int64, error) { + ret := _mock.Called(ctx, userID, cat) + + if len(ret) == 0 { + panic("no return value specified for CountPersonCollections") + } + + var r0 int64 + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, collection.PersonCollectCategory) (int64, error)); ok { + return returnFunc(ctx, userID, cat) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, collection.PersonCollectCategory) int64); ok { + r0 = returnFunc(ctx, userID, cat) + } else { + r0 = ret.Get(0).(int64) + } + if returnFunc, ok := ret.Get(1).(func(context.Context, model.UserID, collection.PersonCollectCategory) error); ok { + r1 = returnFunc(ctx, userID, cat) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// CollectionsRepo_CountPersonCollections_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CountPersonCollections' +type CollectionsRepo_CountPersonCollections_Call struct { + *mock.Call +} + +// CountPersonCollections is a helper method to define mock.On call +// - ctx context.Context +// - userID model.UserID +// - cat collection.PersonCollectCategory +func (_e *CollectionsRepo_Expecter) CountPersonCollections(ctx interface{}, userID interface{}, cat interface{}) *CollectionsRepo_CountPersonCollections_Call { + return &CollectionsRepo_CountPersonCollections_Call{Call: _e.mock.On("CountPersonCollections", ctx, userID, cat)} +} + +func (_c *CollectionsRepo_CountPersonCollections_Call) Run(run func(ctx context.Context, userID model.UserID, cat collection.PersonCollectCategory)) *CollectionsRepo_CountPersonCollections_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.UserID + if args[1] != nil { + arg1 = args[1].(model.UserID) + } + var arg2 collection.PersonCollectCategory + if args[2] != nil { + arg2 = args[2].(collection.PersonCollectCategory) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *CollectionsRepo_CountPersonCollections_Call) Return(n int64, err error) *CollectionsRepo_CountPersonCollections_Call { + _c.Call.Return(n, err) + return _c +} + +func (_c *CollectionsRepo_CountPersonCollections_Call) RunAndReturn(run func(ctx context.Context, userID model.UserID, cat collection.PersonCollectCategory) (int64, error)) *CollectionsRepo_CountPersonCollections_Call { + _c.Call.Return(run) + return _c +} + +// CountSubjectCollections provides a mock function for the type CollectionsRepo +func (_mock *CollectionsRepo) CountSubjectCollections(ctx context.Context, userID model.UserID, subjectType model.SubjectType, collectionType collection.SubjectCollection, showPrivate bool) (int64, error) { + ret := _mock.Called(ctx, userID, subjectType, collectionType, showPrivate) + + if len(ret) == 0 { + panic("no return value specified for CountSubjectCollections") + } + + var r0 int64 + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, model.SubjectType, collection.SubjectCollection, bool) (int64, error)); ok { + return returnFunc(ctx, userID, subjectType, collectionType, showPrivate) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, model.SubjectType, collection.SubjectCollection, bool) int64); ok { + r0 = returnFunc(ctx, userID, subjectType, collectionType, showPrivate) + } else { + r0 = ret.Get(0).(int64) + } + if returnFunc, ok := ret.Get(1).(func(context.Context, model.UserID, model.SubjectType, collection.SubjectCollection, bool) error); ok { + r1 = returnFunc(ctx, userID, subjectType, collectionType, showPrivate) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// CollectionsRepo_CountSubjectCollections_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CountSubjectCollections' +type CollectionsRepo_CountSubjectCollections_Call struct { + *mock.Call +} + +// CountSubjectCollections is a helper method to define mock.On call +// - ctx context.Context +// - userID model.UserID +// - subjectType model.SubjectType +// - collectionType collection.SubjectCollection +// - showPrivate bool +func (_e *CollectionsRepo_Expecter) CountSubjectCollections(ctx interface{}, userID interface{}, subjectType interface{}, collectionType interface{}, showPrivate interface{}) *CollectionsRepo_CountSubjectCollections_Call { + return &CollectionsRepo_CountSubjectCollections_Call{Call: _e.mock.On("CountSubjectCollections", ctx, userID, subjectType, collectionType, showPrivate)} +} + +func (_c *CollectionsRepo_CountSubjectCollections_Call) Run(run func(ctx context.Context, userID model.UserID, subjectType model.SubjectType, collectionType collection.SubjectCollection, showPrivate bool)) *CollectionsRepo_CountSubjectCollections_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.UserID + if args[1] != nil { + arg1 = args[1].(model.UserID) + } + var arg2 model.SubjectType + if args[2] != nil { + arg2 = args[2].(model.SubjectType) + } + var arg3 collection.SubjectCollection + if args[3] != nil { + arg3 = args[3].(collection.SubjectCollection) + } + var arg4 bool + if args[4] != nil { + arg4 = args[4].(bool) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + ) + }) + return _c +} + +func (_c *CollectionsRepo_CountSubjectCollections_Call) Return(n int64, err error) *CollectionsRepo_CountSubjectCollections_Call { + _c.Call.Return(n, err) + return _c +} + +func (_c *CollectionsRepo_CountSubjectCollections_Call) RunAndReturn(run func(ctx context.Context, userID model.UserID, subjectType model.SubjectType, collectionType collection.SubjectCollection, showPrivate bool) (int64, error)) *CollectionsRepo_CountSubjectCollections_Call { + _c.Call.Return(run) + return _c +} + +// GetPersonCollection provides a mock function for the type CollectionsRepo +func (_mock *CollectionsRepo) GetPersonCollection(ctx context.Context, userID model.UserID, cat collection.PersonCollectCategory, targetID model.PersonID) (collection.UserPersonCollection, error) { + ret := _mock.Called(ctx, userID, cat, targetID) + + if len(ret) == 0 { + panic("no return value specified for GetPersonCollection") + } + + var r0 collection.UserPersonCollection + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, collection.PersonCollectCategory, model.PersonID) (collection.UserPersonCollection, error)); ok { + return returnFunc(ctx, userID, cat, targetID) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, collection.PersonCollectCategory, model.PersonID) collection.UserPersonCollection); ok { + r0 = returnFunc(ctx, userID, cat, targetID) + } else { + r0 = ret.Get(0).(collection.UserPersonCollection) + } + if returnFunc, ok := ret.Get(1).(func(context.Context, model.UserID, collection.PersonCollectCategory, model.PersonID) error); ok { + r1 = returnFunc(ctx, userID, cat, targetID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// CollectionsRepo_GetPersonCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPersonCollection' +type CollectionsRepo_GetPersonCollection_Call struct { + *mock.Call +} + +// GetPersonCollection is a helper method to define mock.On call +// - ctx context.Context +// - userID model.UserID +// - cat collection.PersonCollectCategory +// - targetID model.PersonID +func (_e *CollectionsRepo_Expecter) GetPersonCollection(ctx interface{}, userID interface{}, cat interface{}, targetID interface{}) *CollectionsRepo_GetPersonCollection_Call { + return &CollectionsRepo_GetPersonCollection_Call{Call: _e.mock.On("GetPersonCollection", ctx, userID, cat, targetID)} +} + +func (_c *CollectionsRepo_GetPersonCollection_Call) Run(run func(ctx context.Context, userID model.UserID, cat collection.PersonCollectCategory, targetID model.PersonID)) *CollectionsRepo_GetPersonCollection_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.UserID + if args[1] != nil { + arg1 = args[1].(model.UserID) + } + var arg2 collection.PersonCollectCategory + if args[2] != nil { + arg2 = args[2].(collection.PersonCollectCategory) + } + var arg3 model.PersonID + if args[3] != nil { + arg3 = args[3].(model.PersonID) + } + run( + arg0, + arg1, + arg2, + arg3, + ) + }) + return _c +} + +func (_c *CollectionsRepo_GetPersonCollection_Call) Return(userPersonCollection collection.UserPersonCollection, err error) *CollectionsRepo_GetPersonCollection_Call { + _c.Call.Return(userPersonCollection, err) + return _c +} + +func (_c *CollectionsRepo_GetPersonCollection_Call) RunAndReturn(run func(ctx context.Context, userID model.UserID, cat collection.PersonCollectCategory, targetID model.PersonID) (collection.UserPersonCollection, error)) *CollectionsRepo_GetPersonCollection_Call { + _c.Call.Return(run) + return _c +} + +// GetSubjectCollection provides a mock function for the type CollectionsRepo +func (_mock *CollectionsRepo) GetSubjectCollection(ctx context.Context, userID model.UserID, subjectID model.SubjectID) (collection.UserSubjectCollection, error) { + ret := _mock.Called(ctx, userID, subjectID) + + if len(ret) == 0 { + panic("no return value specified for GetSubjectCollection") + } + + var r0 collection.UserSubjectCollection + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, model.SubjectID) (collection.UserSubjectCollection, error)); ok { + return returnFunc(ctx, userID, subjectID) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, model.SubjectID) collection.UserSubjectCollection); ok { + r0 = returnFunc(ctx, userID, subjectID) + } else { + r0 = ret.Get(0).(collection.UserSubjectCollection) + } + if returnFunc, ok := ret.Get(1).(func(context.Context, model.UserID, model.SubjectID) error); ok { + r1 = returnFunc(ctx, userID, subjectID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// CollectionsRepo_GetSubjectCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSubjectCollection' +type CollectionsRepo_GetSubjectCollection_Call struct { + *mock.Call +} + +// GetSubjectCollection is a helper method to define mock.On call +// - ctx context.Context +// - userID model.UserID +// - subjectID model.SubjectID +func (_e *CollectionsRepo_Expecter) GetSubjectCollection(ctx interface{}, userID interface{}, subjectID interface{}) *CollectionsRepo_GetSubjectCollection_Call { + return &CollectionsRepo_GetSubjectCollection_Call{Call: _e.mock.On("GetSubjectCollection", ctx, userID, subjectID)} +} + +func (_c *CollectionsRepo_GetSubjectCollection_Call) Run(run func(ctx context.Context, userID model.UserID, subjectID model.SubjectID)) *CollectionsRepo_GetSubjectCollection_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.UserID + if args[1] != nil { + arg1 = args[1].(model.UserID) + } + var arg2 model.SubjectID + if args[2] != nil { + arg2 = args[2].(model.SubjectID) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *CollectionsRepo_GetSubjectCollection_Call) Return(userSubjectCollection collection.UserSubjectCollection, err error) *CollectionsRepo_GetSubjectCollection_Call { + _c.Call.Return(userSubjectCollection, err) + return _c +} + +func (_c *CollectionsRepo_GetSubjectCollection_Call) RunAndReturn(run func(ctx context.Context, userID model.UserID, subjectID model.SubjectID) (collection.UserSubjectCollection, error)) *CollectionsRepo_GetSubjectCollection_Call { + _c.Call.Return(run) + return _c +} + +// GetSubjectEpisodesCollection provides a mock function for the type CollectionsRepo +func (_mock *CollectionsRepo) GetSubjectEpisodesCollection(ctx context.Context, userID model.UserID, subjectID model.SubjectID) (collection.UserSubjectEpisodesCollection, error) { + ret := _mock.Called(ctx, userID, subjectID) + + if len(ret) == 0 { + panic("no return value specified for GetSubjectEpisodesCollection") + } + + var r0 collection.UserSubjectEpisodesCollection + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, model.SubjectID) (collection.UserSubjectEpisodesCollection, error)); ok { + return returnFunc(ctx, userID, subjectID) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, model.SubjectID) collection.UserSubjectEpisodesCollection); ok { + r0 = returnFunc(ctx, userID, subjectID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(collection.UserSubjectEpisodesCollection) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, model.UserID, model.SubjectID) error); ok { + r1 = returnFunc(ctx, userID, subjectID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// CollectionsRepo_GetSubjectEpisodesCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSubjectEpisodesCollection' +type CollectionsRepo_GetSubjectEpisodesCollection_Call struct { + *mock.Call +} + +// GetSubjectEpisodesCollection is a helper method to define mock.On call +// - ctx context.Context +// - userID model.UserID +// - subjectID model.SubjectID +func (_e *CollectionsRepo_Expecter) GetSubjectEpisodesCollection(ctx interface{}, userID interface{}, subjectID interface{}) *CollectionsRepo_GetSubjectEpisodesCollection_Call { + return &CollectionsRepo_GetSubjectEpisodesCollection_Call{Call: _e.mock.On("GetSubjectEpisodesCollection", ctx, userID, subjectID)} +} + +func (_c *CollectionsRepo_GetSubjectEpisodesCollection_Call) Run(run func(ctx context.Context, userID model.UserID, subjectID model.SubjectID)) *CollectionsRepo_GetSubjectEpisodesCollection_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.UserID + if args[1] != nil { + arg1 = args[1].(model.UserID) + } + var arg2 model.SubjectID + if args[2] != nil { + arg2 = args[2].(model.SubjectID) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *CollectionsRepo_GetSubjectEpisodesCollection_Call) Return(userSubjectEpisodesCollection collection.UserSubjectEpisodesCollection, err error) *CollectionsRepo_GetSubjectEpisodesCollection_Call { + _c.Call.Return(userSubjectEpisodesCollection, err) + return _c +} + +func (_c *CollectionsRepo_GetSubjectEpisodesCollection_Call) RunAndReturn(run func(ctx context.Context, userID model.UserID, subjectID model.SubjectID) (collection.UserSubjectEpisodesCollection, error)) *CollectionsRepo_GetSubjectEpisodesCollection_Call { + _c.Call.Return(run) + return _c +} + +// ListPersonCollection provides a mock function for the type CollectionsRepo +func (_mock *CollectionsRepo) ListPersonCollection(ctx context.Context, userID model.UserID, cat collection.PersonCollectCategory, limit int, offset int) ([]collection.UserPersonCollection, error) { + ret := _mock.Called(ctx, userID, cat, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for ListPersonCollection") + } + + var r0 []collection.UserPersonCollection + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, collection.PersonCollectCategory, int, int) ([]collection.UserPersonCollection, error)); ok { + return returnFunc(ctx, userID, cat, limit, offset) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, collection.PersonCollectCategory, int, int) []collection.UserPersonCollection); ok { + r0 = returnFunc(ctx, userID, cat, limit, offset) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]collection.UserPersonCollection) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, model.UserID, collection.PersonCollectCategory, int, int) error); ok { + r1 = returnFunc(ctx, userID, cat, limit, offset) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// CollectionsRepo_ListPersonCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListPersonCollection' +type CollectionsRepo_ListPersonCollection_Call struct { + *mock.Call +} + +// ListPersonCollection is a helper method to define mock.On call +// - ctx context.Context +// - userID model.UserID +// - cat collection.PersonCollectCategory +// - limit int +// - offset int +func (_e *CollectionsRepo_Expecter) ListPersonCollection(ctx interface{}, userID interface{}, cat interface{}, limit interface{}, offset interface{}) *CollectionsRepo_ListPersonCollection_Call { + return &CollectionsRepo_ListPersonCollection_Call{Call: _e.mock.On("ListPersonCollection", ctx, userID, cat, limit, offset)} +} + +func (_c *CollectionsRepo_ListPersonCollection_Call) Run(run func(ctx context.Context, userID model.UserID, cat collection.PersonCollectCategory, limit int, offset int)) *CollectionsRepo_ListPersonCollection_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.UserID + if args[1] != nil { + arg1 = args[1].(model.UserID) + } + var arg2 collection.PersonCollectCategory + if args[2] != nil { + arg2 = args[2].(collection.PersonCollectCategory) + } + var arg3 int + if args[3] != nil { + arg3 = args[3].(int) + } + var arg4 int + if args[4] != nil { + arg4 = args[4].(int) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + ) + }) + return _c +} + +func (_c *CollectionsRepo_ListPersonCollection_Call) Return(userPersonCollections []collection.UserPersonCollection, err error) *CollectionsRepo_ListPersonCollection_Call { + _c.Call.Return(userPersonCollections, err) + return _c +} + +func (_c *CollectionsRepo_ListPersonCollection_Call) RunAndReturn(run func(ctx context.Context, userID model.UserID, cat collection.PersonCollectCategory, limit int, offset int) ([]collection.UserPersonCollection, error)) *CollectionsRepo_ListPersonCollection_Call { + _c.Call.Return(run) + return _c +} + +// ListSubjectCollection provides a mock function for the type CollectionsRepo +func (_mock *CollectionsRepo) ListSubjectCollection(ctx context.Context, userID model.UserID, subjectType model.SubjectType, collectionType collection.SubjectCollection, showPrivate bool, limit int, offset int) ([]collection.UserSubjectCollection, error) { + ret := _mock.Called(ctx, userID, subjectType, collectionType, showPrivate, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for ListSubjectCollection") + } + + var r0 []collection.UserSubjectCollection + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, model.SubjectType, collection.SubjectCollection, bool, int, int) ([]collection.UserSubjectCollection, error)); ok { + return returnFunc(ctx, userID, subjectType, collectionType, showPrivate, limit, offset) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, model.SubjectType, collection.SubjectCollection, bool, int, int) []collection.UserSubjectCollection); ok { + r0 = returnFunc(ctx, userID, subjectType, collectionType, showPrivate, limit, offset) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]collection.UserSubjectCollection) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, model.UserID, model.SubjectType, collection.SubjectCollection, bool, int, int) error); ok { + r1 = returnFunc(ctx, userID, subjectType, collectionType, showPrivate, limit, offset) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// CollectionsRepo_ListSubjectCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListSubjectCollection' +type CollectionsRepo_ListSubjectCollection_Call struct { + *mock.Call +} + +// ListSubjectCollection is a helper method to define mock.On call +// - ctx context.Context +// - userID model.UserID +// - subjectType model.SubjectType +// - collectionType collection.SubjectCollection +// - showPrivate bool +// - limit int +// - offset int +func (_e *CollectionsRepo_Expecter) ListSubjectCollection(ctx interface{}, userID interface{}, subjectType interface{}, collectionType interface{}, showPrivate interface{}, limit interface{}, offset interface{}) *CollectionsRepo_ListSubjectCollection_Call { + return &CollectionsRepo_ListSubjectCollection_Call{Call: _e.mock.On("ListSubjectCollection", ctx, userID, subjectType, collectionType, showPrivate, limit, offset)} +} + +func (_c *CollectionsRepo_ListSubjectCollection_Call) Run(run func(ctx context.Context, userID model.UserID, subjectType model.SubjectType, collectionType collection.SubjectCollection, showPrivate bool, limit int, offset int)) *CollectionsRepo_ListSubjectCollection_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.UserID + if args[1] != nil { + arg1 = args[1].(model.UserID) + } + var arg2 model.SubjectType + if args[2] != nil { + arg2 = args[2].(model.SubjectType) + } + var arg3 collection.SubjectCollection + if args[3] != nil { + arg3 = args[3].(collection.SubjectCollection) + } + var arg4 bool + if args[4] != nil { + arg4 = args[4].(bool) + } + var arg5 int + if args[5] != nil { + arg5 = args[5].(int) + } + var arg6 int + if args[6] != nil { + arg6 = args[6].(int) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + arg6, + ) + }) + return _c +} + +func (_c *CollectionsRepo_ListSubjectCollection_Call) Return(userSubjectCollections []collection.UserSubjectCollection, err error) *CollectionsRepo_ListSubjectCollection_Call { + _c.Call.Return(userSubjectCollections, err) + return _c +} + +func (_c *CollectionsRepo_ListSubjectCollection_Call) RunAndReturn(run func(ctx context.Context, userID model.UserID, subjectType model.SubjectType, collectionType collection.SubjectCollection, showPrivate bool, limit int, offset int) ([]collection.UserSubjectCollection, error)) *CollectionsRepo_ListSubjectCollection_Call { + _c.Call.Return(run) + return _c +} + +// RemovePersonCollection provides a mock function for the type CollectionsRepo +func (_mock *CollectionsRepo) RemovePersonCollection(ctx context.Context, userID model.UserID, cat collection.PersonCollectCategory, targetID model.PersonID) error { + ret := _mock.Called(ctx, userID, cat, targetID) + + if len(ret) == 0 { + panic("no return value specified for RemovePersonCollection") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, collection.PersonCollectCategory, model.PersonID) error); ok { + r0 = returnFunc(ctx, userID, cat, targetID) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// CollectionsRepo_RemovePersonCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemovePersonCollection' +type CollectionsRepo_RemovePersonCollection_Call struct { + *mock.Call +} + +// RemovePersonCollection is a helper method to define mock.On call +// - ctx context.Context +// - userID model.UserID +// - cat collection.PersonCollectCategory +// - targetID model.PersonID +func (_e *CollectionsRepo_Expecter) RemovePersonCollection(ctx interface{}, userID interface{}, cat interface{}, targetID interface{}) *CollectionsRepo_RemovePersonCollection_Call { + return &CollectionsRepo_RemovePersonCollection_Call{Call: _e.mock.On("RemovePersonCollection", ctx, userID, cat, targetID)} +} + +func (_c *CollectionsRepo_RemovePersonCollection_Call) Run(run func(ctx context.Context, userID model.UserID, cat collection.PersonCollectCategory, targetID model.PersonID)) *CollectionsRepo_RemovePersonCollection_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.UserID + if args[1] != nil { + arg1 = args[1].(model.UserID) + } + var arg2 collection.PersonCollectCategory + if args[2] != nil { + arg2 = args[2].(collection.PersonCollectCategory) + } + var arg3 model.PersonID + if args[3] != nil { + arg3 = args[3].(model.PersonID) + } + run( + arg0, + arg1, + arg2, + arg3, + ) + }) + return _c +} + +func (_c *CollectionsRepo_RemovePersonCollection_Call) Return(err error) *CollectionsRepo_RemovePersonCollection_Call { + _c.Call.Return(err) + return _c +} + +func (_c *CollectionsRepo_RemovePersonCollection_Call) RunAndReturn(run func(ctx context.Context, userID model.UserID, cat collection.PersonCollectCategory, targetID model.PersonID) error) *CollectionsRepo_RemovePersonCollection_Call { + _c.Call.Return(run) + return _c +} + +// UpdateEpisodeCollection provides a mock function for the type CollectionsRepo +func (_mock *CollectionsRepo) UpdateEpisodeCollection(ctx context.Context, userID model.UserID, subjectID model.SubjectID, episodeIDs []model.EpisodeID, collection1 collection.EpisodeCollection, at time.Time) (collection.UserSubjectEpisodesCollection, error) { + ret := _mock.Called(ctx, userID, subjectID, episodeIDs, collection1, at) + + if len(ret) == 0 { + panic("no return value specified for UpdateEpisodeCollection") + } + + var r0 collection.UserSubjectEpisodesCollection + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, model.SubjectID, []model.EpisodeID, collection.EpisodeCollection, time.Time) (collection.UserSubjectEpisodesCollection, error)); ok { + return returnFunc(ctx, userID, subjectID, episodeIDs, collection1, at) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, model.SubjectID, []model.EpisodeID, collection.EpisodeCollection, time.Time) collection.UserSubjectEpisodesCollection); ok { + r0 = returnFunc(ctx, userID, subjectID, episodeIDs, collection1, at) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(collection.UserSubjectEpisodesCollection) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, model.UserID, model.SubjectID, []model.EpisodeID, collection.EpisodeCollection, time.Time) error); ok { + r1 = returnFunc(ctx, userID, subjectID, episodeIDs, collection1, at) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// CollectionsRepo_UpdateEpisodeCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateEpisodeCollection' +type CollectionsRepo_UpdateEpisodeCollection_Call struct { + *mock.Call +} + +// UpdateEpisodeCollection is a helper method to define mock.On call +// - ctx context.Context +// - userID model.UserID +// - subjectID model.SubjectID +// - episodeIDs []model.EpisodeID +// - collection1 collection.EpisodeCollection +// - at time.Time +func (_e *CollectionsRepo_Expecter) UpdateEpisodeCollection(ctx interface{}, userID interface{}, subjectID interface{}, episodeIDs interface{}, collection1 interface{}, at interface{}) *CollectionsRepo_UpdateEpisodeCollection_Call { + return &CollectionsRepo_UpdateEpisodeCollection_Call{Call: _e.mock.On("UpdateEpisodeCollection", ctx, userID, subjectID, episodeIDs, collection1, at)} +} + +func (_c *CollectionsRepo_UpdateEpisodeCollection_Call) Run(run func(ctx context.Context, userID model.UserID, subjectID model.SubjectID, episodeIDs []model.EpisodeID, collection1 collection.EpisodeCollection, at time.Time)) *CollectionsRepo_UpdateEpisodeCollection_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.UserID + if args[1] != nil { + arg1 = args[1].(model.UserID) + } + var arg2 model.SubjectID + if args[2] != nil { + arg2 = args[2].(model.SubjectID) + } + var arg3 []model.EpisodeID + if args[3] != nil { + arg3 = args[3].([]model.EpisodeID) + } + var arg4 collection.EpisodeCollection + if args[4] != nil { + arg4 = args[4].(collection.EpisodeCollection) + } + var arg5 time.Time + if args[5] != nil { + arg5 = args[5].(time.Time) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + ) + }) + return _c +} + +func (_c *CollectionsRepo_UpdateEpisodeCollection_Call) Return(userSubjectEpisodesCollection collection.UserSubjectEpisodesCollection, err error) *CollectionsRepo_UpdateEpisodeCollection_Call { + _c.Call.Return(userSubjectEpisodesCollection, err) + return _c +} + +func (_c *CollectionsRepo_UpdateEpisodeCollection_Call) RunAndReturn(run func(ctx context.Context, userID model.UserID, subjectID model.SubjectID, episodeIDs []model.EpisodeID, collection1 collection.EpisodeCollection, at time.Time) (collection.UserSubjectEpisodesCollection, error)) *CollectionsRepo_UpdateEpisodeCollection_Call { + _c.Call.Return(run) + return _c +} + +// UpdateOrCreateSubjectCollection provides a mock function for the type CollectionsRepo +func (_mock *CollectionsRepo) UpdateOrCreateSubjectCollection(ctx context.Context, userID model.UserID, subject model.Subject, at time.Time, ip string, update func(ctx context.Context, s *collection.Subject) (*collection.Subject, error)) error { + ret := _mock.Called(ctx, userID, subject, at, ip, update) + + if len(ret) == 0 { + panic("no return value specified for UpdateOrCreateSubjectCollection") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, model.Subject, time.Time, string, func(ctx context.Context, s *collection.Subject) (*collection.Subject, error)) error); ok { + r0 = returnFunc(ctx, userID, subject, at, ip, update) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// CollectionsRepo_UpdateOrCreateSubjectCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateOrCreateSubjectCollection' +type CollectionsRepo_UpdateOrCreateSubjectCollection_Call struct { + *mock.Call +} + +// UpdateOrCreateSubjectCollection is a helper method to define mock.On call +// - ctx context.Context +// - userID model.UserID +// - subject model.Subject +// - at time.Time +// - ip string +// - update func(ctx context.Context, s *collection.Subject) (*collection.Subject, error) +func (_e *CollectionsRepo_Expecter) UpdateOrCreateSubjectCollection(ctx interface{}, userID interface{}, subject interface{}, at interface{}, ip interface{}, update interface{}) *CollectionsRepo_UpdateOrCreateSubjectCollection_Call { + return &CollectionsRepo_UpdateOrCreateSubjectCollection_Call{Call: _e.mock.On("UpdateOrCreateSubjectCollection", ctx, userID, subject, at, ip, update)} +} + +func (_c *CollectionsRepo_UpdateOrCreateSubjectCollection_Call) Run(run func(ctx context.Context, userID model.UserID, subject model.Subject, at time.Time, ip string, update func(ctx context.Context, s *collection.Subject) (*collection.Subject, error))) *CollectionsRepo_UpdateOrCreateSubjectCollection_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.UserID + if args[1] != nil { + arg1 = args[1].(model.UserID) + } + var arg2 model.Subject + if args[2] != nil { + arg2 = args[2].(model.Subject) + } + var arg3 time.Time + if args[3] != nil { + arg3 = args[3].(time.Time) + } + var arg4 string + if args[4] != nil { + arg4 = args[4].(string) + } + var arg5 func(ctx context.Context, s *collection.Subject) (*collection.Subject, error) + if args[5] != nil { + arg5 = args[5].(func(ctx context.Context, s *collection.Subject) (*collection.Subject, error)) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + ) + }) + return _c +} + +func (_c *CollectionsRepo_UpdateOrCreateSubjectCollection_Call) Return(err error) *CollectionsRepo_UpdateOrCreateSubjectCollection_Call { + _c.Call.Return(err) + return _c +} + +func (_c *CollectionsRepo_UpdateOrCreateSubjectCollection_Call) RunAndReturn(run func(ctx context.Context, userID model.UserID, subject model.Subject, at time.Time, ip string, update func(ctx context.Context, s *collection.Subject) (*collection.Subject, error)) error) *CollectionsRepo_UpdateOrCreateSubjectCollection_Call { + _c.Call.Return(run) + return _c +} + +// UpdateSubjectCollection provides a mock function for the type CollectionsRepo +func (_mock *CollectionsRepo) UpdateSubjectCollection(ctx context.Context, userID model.UserID, subject model.Subject, at time.Time, ip string, update func(ctx context.Context, s *collection.Subject) (*collection.Subject, error)) error { + ret := _mock.Called(ctx, userID, subject, at, ip, update) + + if len(ret) == 0 { + panic("no return value specified for UpdateSubjectCollection") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, model.Subject, time.Time, string, func(ctx context.Context, s *collection.Subject) (*collection.Subject, error)) error); ok { + r0 = returnFunc(ctx, userID, subject, at, ip, update) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// CollectionsRepo_UpdateSubjectCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateSubjectCollection' +type CollectionsRepo_UpdateSubjectCollection_Call struct { + *mock.Call +} + +// UpdateSubjectCollection is a helper method to define mock.On call +// - ctx context.Context +// - userID model.UserID +// - subject model.Subject +// - at time.Time +// - ip string +// - update func(ctx context.Context, s *collection.Subject) (*collection.Subject, error) +func (_e *CollectionsRepo_Expecter) UpdateSubjectCollection(ctx interface{}, userID interface{}, subject interface{}, at interface{}, ip interface{}, update interface{}) *CollectionsRepo_UpdateSubjectCollection_Call { + return &CollectionsRepo_UpdateSubjectCollection_Call{Call: _e.mock.On("UpdateSubjectCollection", ctx, userID, subject, at, ip, update)} +} + +func (_c *CollectionsRepo_UpdateSubjectCollection_Call) Run(run func(ctx context.Context, userID model.UserID, subject model.Subject, at time.Time, ip string, update func(ctx context.Context, s *collection.Subject) (*collection.Subject, error))) *CollectionsRepo_UpdateSubjectCollection_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.UserID + if args[1] != nil { + arg1 = args[1].(model.UserID) + } + var arg2 model.Subject + if args[2] != nil { + arg2 = args[2].(model.Subject) + } + var arg3 time.Time + if args[3] != nil { + arg3 = args[3].(time.Time) + } + var arg4 string + if args[4] != nil { + arg4 = args[4].(string) + } + var arg5 func(ctx context.Context, s *collection.Subject) (*collection.Subject, error) + if args[5] != nil { + arg5 = args[5].(func(ctx context.Context, s *collection.Subject) (*collection.Subject, error)) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + ) + }) + return _c +} + +func (_c *CollectionsRepo_UpdateSubjectCollection_Call) Return(err error) *CollectionsRepo_UpdateSubjectCollection_Call { + _c.Call.Return(err) + return _c +} + +func (_c *CollectionsRepo_UpdateSubjectCollection_Call) RunAndReturn(run func(ctx context.Context, userID model.UserID, subject model.Subject, at time.Time, ip string, update func(ctx context.Context, s *collection.Subject) (*collection.Subject, error)) error) *CollectionsRepo_UpdateSubjectCollection_Call { + _c.Call.Return(run) + return _c +} + +// WithQuery provides a mock function for the type CollectionsRepo +func (_mock *CollectionsRepo) WithQuery(query1 *query.Query) collections.Repo { + ret := _mock.Called(query1) + + if len(ret) == 0 { + panic("no return value specified for WithQuery") + } + + var r0 collections.Repo + if returnFunc, ok := ret.Get(0).(func(*query.Query) collections.Repo); ok { + r0 = returnFunc(query1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(collections.Repo) + } + } + return r0 +} + +// CollectionsRepo_WithQuery_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WithQuery' +type CollectionsRepo_WithQuery_Call struct { + *mock.Call +} + +// WithQuery is a helper method to define mock.On call +// - query1 *query.Query +func (_e *CollectionsRepo_Expecter) WithQuery(query1 interface{}) *CollectionsRepo_WithQuery_Call { + return &CollectionsRepo_WithQuery_Call{Call: _e.mock.On("WithQuery", query1)} +} + +func (_c *CollectionsRepo_WithQuery_Call) Run(run func(query1 *query.Query)) *CollectionsRepo_WithQuery_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 *query.Query + if args[0] != nil { + arg0 = args[0].(*query.Query) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *CollectionsRepo_WithQuery_Call) Return(repo collections.Repo) *CollectionsRepo_WithQuery_Call { + _c.Call.Return(repo) + return _c +} + +func (_c *CollectionsRepo_WithQuery_Call) RunAndReturn(run func(query1 *query.Query) collections.Repo) *CollectionsRepo_WithQuery_Call { + _c.Call.Return(run) + return _c +} diff --git a/internal/mocks/EpisodeRepo.go b/internal/mocks/EpisodeRepo.go index d07424611..068dd0ce6 100644 --- a/internal/mocks/EpisodeRepo.go +++ b/internal/mocks/EpisodeRepo.go @@ -1,16 +1,32 @@ -// Code generated by mockery v2.23.1. DO NOT EDIT. +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify package mocks import ( - context "context" + "context" - episode "github.com/bangumi/server/internal/episode" + "github.com/bangumi/server/dal/query" + "github.com/bangumi/server/internal/episode" + "github.com/bangumi/server/internal/model" mock "github.com/stretchr/testify/mock" - - query "github.com/bangumi/server/dal/query" ) +// NewEpisodeRepo creates a new instance of EpisodeRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewEpisodeRepo(t interface { + mock.TestingT + Cleanup(func()) +}) *EpisodeRepo { + mock := &EpisodeRepo{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + // EpisodeRepo is an autogenerated mock type for the Repo type type EpisodeRepo struct { mock.Mock @@ -24,27 +40,29 @@ func (_m *EpisodeRepo) EXPECT() *EpisodeRepo_Expecter { return &EpisodeRepo_Expecter{mock: &_m.Mock} } -// Count provides a mock function with given fields: ctx, subjectID, filter -func (_m *EpisodeRepo) Count(ctx context.Context, subjectID uint32, filter episode.Filter) (int64, error) { - ret := _m.Called(ctx, subjectID, filter) +// Count provides a mock function for the type EpisodeRepo +func (_mock *EpisodeRepo) Count(ctx context.Context, subjectID model.SubjectID, filter episode.Filter) (int64, error) { + ret := _mock.Called(ctx, subjectID, filter) + + if len(ret) == 0 { + panic("no return value specified for Count") + } var r0 int64 var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, episode.Filter) (int64, error)); ok { - return rf(ctx, subjectID, filter) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID, episode.Filter) (int64, error)); ok { + return returnFunc(ctx, subjectID, filter) } - if rf, ok := ret.Get(0).(func(context.Context, uint32, episode.Filter) int64); ok { - r0 = rf(ctx, subjectID, filter) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID, episode.Filter) int64); ok { + r0 = returnFunc(ctx, subjectID, filter) } else { r0 = ret.Get(0).(int64) } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, episode.Filter) error); ok { - r1 = rf(ctx, subjectID, filter) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.SubjectID, episode.Filter) error); ok { + r1 = returnFunc(ctx, subjectID, filter) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -55,50 +73,68 @@ type EpisodeRepo_Count_Call struct { // Count is a helper method to define mock.On call // - ctx context.Context -// - subjectID uint32 +// - subjectID model.SubjectID // - filter episode.Filter func (_e *EpisodeRepo_Expecter) Count(ctx interface{}, subjectID interface{}, filter interface{}) *EpisodeRepo_Count_Call { return &EpisodeRepo_Count_Call{Call: _e.mock.On("Count", ctx, subjectID, filter)} } -func (_c *EpisodeRepo_Count_Call) Run(run func(ctx context.Context, subjectID uint32, filter episode.Filter)) *EpisodeRepo_Count_Call { +func (_c *EpisodeRepo_Count_Call) Run(run func(ctx context.Context, subjectID model.SubjectID, filter episode.Filter)) *EpisodeRepo_Count_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(episode.Filter)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.SubjectID + if args[1] != nil { + arg1 = args[1].(model.SubjectID) + } + var arg2 episode.Filter + if args[2] != nil { + arg2 = args[2].(episode.Filter) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } -func (_c *EpisodeRepo_Count_Call) Return(_a0 int64, _a1 error) *EpisodeRepo_Count_Call { - _c.Call.Return(_a0, _a1) +func (_c *EpisodeRepo_Count_Call) Return(n int64, err error) *EpisodeRepo_Count_Call { + _c.Call.Return(n, err) return _c } -func (_c *EpisodeRepo_Count_Call) RunAndReturn(run func(context.Context, uint32, episode.Filter) (int64, error)) *EpisodeRepo_Count_Call { +func (_c *EpisodeRepo_Count_Call) RunAndReturn(run func(ctx context.Context, subjectID model.SubjectID, filter episode.Filter) (int64, error)) *EpisodeRepo_Count_Call { _c.Call.Return(run) return _c } -// Get provides a mock function with given fields: ctx, episodeID -func (_m *EpisodeRepo) Get(ctx context.Context, episodeID uint32) (episode.Episode, error) { - ret := _m.Called(ctx, episodeID) +// Get provides a mock function for the type EpisodeRepo +func (_mock *EpisodeRepo) Get(ctx context.Context, episodeID model.EpisodeID) (episode.Episode, error) { + ret := _mock.Called(ctx, episodeID) + + if len(ret) == 0 { + panic("no return value specified for Get") + } var r0 episode.Episode var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (episode.Episode, error)); ok { - return rf(ctx, episodeID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.EpisodeID) (episode.Episode, error)); ok { + return returnFunc(ctx, episodeID) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) episode.Episode); ok { - r0 = rf(ctx, episodeID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.EpisodeID) episode.Episode); ok { + r0 = returnFunc(ctx, episodeID) } else { r0 = ret.Get(0).(episode.Episode) } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, episodeID) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.EpisodeID) error); ok { + r1 = returnFunc(ctx, episodeID) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -109,51 +145,64 @@ type EpisodeRepo_Get_Call struct { // Get is a helper method to define mock.On call // - ctx context.Context -// - episodeID uint32 +// - episodeID model.EpisodeID func (_e *EpisodeRepo_Expecter) Get(ctx interface{}, episodeID interface{}) *EpisodeRepo_Get_Call { return &EpisodeRepo_Get_Call{Call: _e.mock.On("Get", ctx, episodeID)} } -func (_c *EpisodeRepo_Get_Call) Run(run func(ctx context.Context, episodeID uint32)) *EpisodeRepo_Get_Call { +func (_c *EpisodeRepo_Get_Call) Run(run func(ctx context.Context, episodeID model.EpisodeID)) *EpisodeRepo_Get_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.EpisodeID + if args[1] != nil { + arg1 = args[1].(model.EpisodeID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *EpisodeRepo_Get_Call) Return(_a0 episode.Episode, _a1 error) *EpisodeRepo_Get_Call { - _c.Call.Return(_a0, _a1) +func (_c *EpisodeRepo_Get_Call) Return(episode1 episode.Episode, err error) *EpisodeRepo_Get_Call { + _c.Call.Return(episode1, err) return _c } -func (_c *EpisodeRepo_Get_Call) RunAndReturn(run func(context.Context, uint32) (episode.Episode, error)) *EpisodeRepo_Get_Call { +func (_c *EpisodeRepo_Get_Call) RunAndReturn(run func(ctx context.Context, episodeID model.EpisodeID) (episode.Episode, error)) *EpisodeRepo_Get_Call { _c.Call.Return(run) return _c } -// List provides a mock function with given fields: ctx, subjectID, filter, limit, offset -func (_m *EpisodeRepo) List(ctx context.Context, subjectID uint32, filter episode.Filter, limit int, offset int) ([]episode.Episode, error) { - ret := _m.Called(ctx, subjectID, filter, limit, offset) +// List provides a mock function for the type EpisodeRepo +func (_mock *EpisodeRepo) List(ctx context.Context, subjectID model.SubjectID, filter episode.Filter, limit int, offset int) ([]episode.Episode, error) { + ret := _mock.Called(ctx, subjectID, filter, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for List") + } var r0 []episode.Episode var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, episode.Filter, int, int) ([]episode.Episode, error)); ok { - return rf(ctx, subjectID, filter, limit, offset) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID, episode.Filter, int, int) ([]episode.Episode, error)); ok { + return returnFunc(ctx, subjectID, filter, limit, offset) } - if rf, ok := ret.Get(0).(func(context.Context, uint32, episode.Filter, int, int) []episode.Episode); ok { - r0 = rf(ctx, subjectID, filter, limit, offset) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID, episode.Filter, int, int) []episode.Episode); ok { + r0 = returnFunc(ctx, subjectID, filter, limit, offset) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]episode.Episode) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, episode.Filter, int, int) error); ok { - r1 = rf(ctx, subjectID, filter, limit, offset) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.SubjectID, episode.Filter, int, int) error); ok { + r1 = returnFunc(ctx, subjectID, filter, limit, offset) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -164,7 +213,7 @@ type EpisodeRepo_List_Call struct { // List is a helper method to define mock.On call // - ctx context.Context -// - subjectID uint32 +// - subjectID model.SubjectID // - filter episode.Filter // - limit int // - offset int @@ -172,36 +221,65 @@ func (_e *EpisodeRepo_Expecter) List(ctx interface{}, subjectID interface{}, fil return &EpisodeRepo_List_Call{Call: _e.mock.On("List", ctx, subjectID, filter, limit, offset)} } -func (_c *EpisodeRepo_List_Call) Run(run func(ctx context.Context, subjectID uint32, filter episode.Filter, limit int, offset int)) *EpisodeRepo_List_Call { +func (_c *EpisodeRepo_List_Call) Run(run func(ctx context.Context, subjectID model.SubjectID, filter episode.Filter, limit int, offset int)) *EpisodeRepo_List_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(episode.Filter), args[3].(int), args[4].(int)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.SubjectID + if args[1] != nil { + arg1 = args[1].(model.SubjectID) + } + var arg2 episode.Filter + if args[2] != nil { + arg2 = args[2].(episode.Filter) + } + var arg3 int + if args[3] != nil { + arg3 = args[3].(int) + } + var arg4 int + if args[4] != nil { + arg4 = args[4].(int) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + ) }) return _c } -func (_c *EpisodeRepo_List_Call) Return(_a0 []episode.Episode, _a1 error) *EpisodeRepo_List_Call { - _c.Call.Return(_a0, _a1) +func (_c *EpisodeRepo_List_Call) Return(episodes []episode.Episode, err error) *EpisodeRepo_List_Call { + _c.Call.Return(episodes, err) return _c } -func (_c *EpisodeRepo_List_Call) RunAndReturn(run func(context.Context, uint32, episode.Filter, int, int) ([]episode.Episode, error)) *EpisodeRepo_List_Call { +func (_c *EpisodeRepo_List_Call) RunAndReturn(run func(ctx context.Context, subjectID model.SubjectID, filter episode.Filter, limit int, offset int) ([]episode.Episode, error)) *EpisodeRepo_List_Call { _c.Call.Return(run) return _c } -// WithQuery provides a mock function with given fields: _a0 -func (_m *EpisodeRepo) WithQuery(_a0 *query.Query) episode.Repo { - ret := _m.Called(_a0) +// WithQuery provides a mock function for the type EpisodeRepo +func (_mock *EpisodeRepo) WithQuery(query1 *query.Query) episode.Repo { + ret := _mock.Called(query1) + + if len(ret) == 0 { + panic("no return value specified for WithQuery") + } var r0 episode.Repo - if rf, ok := ret.Get(0).(func(*query.Query) episode.Repo); ok { - r0 = rf(_a0) + if returnFunc, ok := ret.Get(0).(func(*query.Query) episode.Repo); ok { + r0 = returnFunc(query1) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(episode.Repo) } } - return r0 } @@ -211,39 +289,30 @@ type EpisodeRepo_WithQuery_Call struct { } // WithQuery is a helper method to define mock.On call -// - _a0 *query.Query -func (_e *EpisodeRepo_Expecter) WithQuery(_a0 interface{}) *EpisodeRepo_WithQuery_Call { - return &EpisodeRepo_WithQuery_Call{Call: _e.mock.On("WithQuery", _a0)} +// - query1 *query.Query +func (_e *EpisodeRepo_Expecter) WithQuery(query1 interface{}) *EpisodeRepo_WithQuery_Call { + return &EpisodeRepo_WithQuery_Call{Call: _e.mock.On("WithQuery", query1)} } -func (_c *EpisodeRepo_WithQuery_Call) Run(run func(_a0 *query.Query)) *EpisodeRepo_WithQuery_Call { +func (_c *EpisodeRepo_WithQuery_Call) Run(run func(query1 *query.Query)) *EpisodeRepo_WithQuery_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*query.Query)) + var arg0 *query.Query + if args[0] != nil { + arg0 = args[0].(*query.Query) + } + run( + arg0, + ) }) return _c } -func (_c *EpisodeRepo_WithQuery_Call) Return(_a0 episode.Repo) *EpisodeRepo_WithQuery_Call { - _c.Call.Return(_a0) +func (_c *EpisodeRepo_WithQuery_Call) Return(repo episode.Repo) *EpisodeRepo_WithQuery_Call { + _c.Call.Return(repo) return _c } -func (_c *EpisodeRepo_WithQuery_Call) RunAndReturn(run func(*query.Query) episode.Repo) *EpisodeRepo_WithQuery_Call { +func (_c *EpisodeRepo_WithQuery_Call) RunAndReturn(run func(query1 *query.Query) episode.Repo) *EpisodeRepo_WithQuery_Call { _c.Call.Return(run) return _c } - -type mockConstructorTestingTNewEpisodeRepo interface { - mock.TestingT - Cleanup(func()) -} - -// NewEpisodeRepo creates a new instance of EpisodeRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewEpisodeRepo(t mockConstructorTestingTNewEpisodeRepo) *EpisodeRepo { - mock := &EpisodeRepo{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/mocks/IndexRepo.go b/internal/mocks/IndexRepo.go index 8090e01da..999ffec5e 100644 --- a/internal/mocks/IndexRepo.go +++ b/internal/mocks/IndexRepo.go @@ -1,16 +1,31 @@ -// Code generated by mockery v2.23.1. DO NOT EDIT. +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify package mocks import ( - context "context" + "context" - index "github.com/bangumi/server/internal/index" + "github.com/bangumi/server/internal/index" + "github.com/bangumi/server/internal/model" mock "github.com/stretchr/testify/mock" - - model "github.com/bangumi/server/internal/model" ) +// NewIndexRepo creates a new instance of IndexRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewIndexRepo(t interface { + mock.TestingT + Cleanup(func()) +}) *IndexRepo { + mock := &IndexRepo{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + // IndexRepo is an autogenerated mock type for the Repo type type IndexRepo struct { mock.Mock @@ -24,17 +39,20 @@ func (_m *IndexRepo) EXPECT() *IndexRepo_Expecter { return &IndexRepo_Expecter{mock: &_m.Mock} } -// AddIndexCollect provides a mock function with given fields: ctx, id, uid -func (_m *IndexRepo) AddIndexCollect(ctx context.Context, id uint32, uid uint32) error { - ret := _m.Called(ctx, id, uid) +// AddIndexCollect provides a mock function for the type IndexRepo +func (_mock *IndexRepo) AddIndexCollect(ctx context.Context, id model.IndexID, uid model.UserID) error { + ret := _mock.Called(ctx, id, uid) + + if len(ret) == 0 { + panic("no return value specified for AddIndexCollect") + } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint32) error); ok { - r0 = rf(ctx, id, uid) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.IndexID, model.UserID) error); ok { + r0 = returnFunc(ctx, id, uid) } else { r0 = ret.Error(0) } - return r0 } @@ -45,52 +63,70 @@ type IndexRepo_AddIndexCollect_Call struct { // AddIndexCollect is a helper method to define mock.On call // - ctx context.Context -// - id uint32 -// - uid uint32 +// - id model.IndexID +// - uid model.UserID func (_e *IndexRepo_Expecter) AddIndexCollect(ctx interface{}, id interface{}, uid interface{}) *IndexRepo_AddIndexCollect_Call { return &IndexRepo_AddIndexCollect_Call{Call: _e.mock.On("AddIndexCollect", ctx, id, uid)} } -func (_c *IndexRepo_AddIndexCollect_Call) Run(run func(ctx context.Context, id uint32, uid uint32)) *IndexRepo_AddIndexCollect_Call { +func (_c *IndexRepo_AddIndexCollect_Call) Run(run func(ctx context.Context, id model.IndexID, uid model.UserID)) *IndexRepo_AddIndexCollect_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.IndexID + if args[1] != nil { + arg1 = args[1].(model.IndexID) + } + var arg2 model.UserID + if args[2] != nil { + arg2 = args[2].(model.UserID) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } -func (_c *IndexRepo_AddIndexCollect_Call) Return(_a0 error) *IndexRepo_AddIndexCollect_Call { - _c.Call.Return(_a0) +func (_c *IndexRepo_AddIndexCollect_Call) Return(err error) *IndexRepo_AddIndexCollect_Call { + _c.Call.Return(err) return _c } -func (_c *IndexRepo_AddIndexCollect_Call) RunAndReturn(run func(context.Context, uint32, uint32) error) *IndexRepo_AddIndexCollect_Call { +func (_c *IndexRepo_AddIndexCollect_Call) RunAndReturn(run func(ctx context.Context, id model.IndexID, uid model.UserID) error) *IndexRepo_AddIndexCollect_Call { _c.Call.Return(run) return _c } -// AddOrUpdateIndexSubject provides a mock function with given fields: ctx, id, subjectID, sort, comment -func (_m *IndexRepo) AddOrUpdateIndexSubject(ctx context.Context, id uint32, subjectID uint32, sort uint32, comment string) (*index.Subject, error) { - ret := _m.Called(ctx, id, subjectID, sort, comment) +// AddOrUpdateIndexSubject provides a mock function for the type IndexRepo +func (_mock *IndexRepo) AddOrUpdateIndexSubject(ctx context.Context, id model.IndexID, subjectID model.SubjectID, sort uint32, comment string) (*index.Subject, error) { + ret := _mock.Called(ctx, id, subjectID, sort, comment) + + if len(ret) == 0 { + panic("no return value specified for AddOrUpdateIndexSubject") + } var r0 *index.Subject var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint32, uint32, string) (*index.Subject, error)); ok { - return rf(ctx, id, subjectID, sort, comment) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.IndexID, model.SubjectID, uint32, string) (*index.Subject, error)); ok { + return returnFunc(ctx, id, subjectID, sort, comment) } - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint32, uint32, string) *index.Subject); ok { - r0 = rf(ctx, id, subjectID, sort, comment) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.IndexID, model.SubjectID, uint32, string) *index.Subject); ok { + r0 = returnFunc(ctx, id, subjectID, sort, comment) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*index.Subject) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, uint32, uint32, string) error); ok { - r1 = rf(ctx, id, subjectID, sort, comment) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.IndexID, model.SubjectID, uint32, string) error); ok { + r1 = returnFunc(ctx, id, subjectID, sort, comment) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -101,52 +137,80 @@ type IndexRepo_AddOrUpdateIndexSubject_Call struct { // AddOrUpdateIndexSubject is a helper method to define mock.On call // - ctx context.Context -// - id uint32 -// - subjectID uint32 +// - id model.IndexID +// - subjectID model.SubjectID // - sort uint32 // - comment string func (_e *IndexRepo_Expecter) AddOrUpdateIndexSubject(ctx interface{}, id interface{}, subjectID interface{}, sort interface{}, comment interface{}) *IndexRepo_AddOrUpdateIndexSubject_Call { return &IndexRepo_AddOrUpdateIndexSubject_Call{Call: _e.mock.On("AddOrUpdateIndexSubject", ctx, id, subjectID, sort, comment)} } -func (_c *IndexRepo_AddOrUpdateIndexSubject_Call) Run(run func(ctx context.Context, id uint32, subjectID uint32, sort uint32, comment string)) *IndexRepo_AddOrUpdateIndexSubject_Call { +func (_c *IndexRepo_AddOrUpdateIndexSubject_Call) Run(run func(ctx context.Context, id model.IndexID, subjectID model.SubjectID, sort uint32, comment string)) *IndexRepo_AddOrUpdateIndexSubject_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(uint32), args[3].(uint32), args[4].(string)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.IndexID + if args[1] != nil { + arg1 = args[1].(model.IndexID) + } + var arg2 model.SubjectID + if args[2] != nil { + arg2 = args[2].(model.SubjectID) + } + var arg3 uint32 + if args[3] != nil { + arg3 = args[3].(uint32) + } + var arg4 string + if args[4] != nil { + arg4 = args[4].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + ) }) return _c } -func (_c *IndexRepo_AddOrUpdateIndexSubject_Call) Return(_a0 *index.Subject, _a1 error) *IndexRepo_AddOrUpdateIndexSubject_Call { - _c.Call.Return(_a0, _a1) +func (_c *IndexRepo_AddOrUpdateIndexSubject_Call) Return(subject *index.Subject, err error) *IndexRepo_AddOrUpdateIndexSubject_Call { + _c.Call.Return(subject, err) return _c } -func (_c *IndexRepo_AddOrUpdateIndexSubject_Call) RunAndReturn(run func(context.Context, uint32, uint32, uint32, string) (*index.Subject, error)) *IndexRepo_AddOrUpdateIndexSubject_Call { +func (_c *IndexRepo_AddOrUpdateIndexSubject_Call) RunAndReturn(run func(ctx context.Context, id model.IndexID, subjectID model.SubjectID, sort uint32, comment string) (*index.Subject, error)) *IndexRepo_AddOrUpdateIndexSubject_Call { _c.Call.Return(run) return _c } -// CountSubjects provides a mock function with given fields: ctx, id, subjectType -func (_m *IndexRepo) CountSubjects(ctx context.Context, id uint32, subjectType uint8) (int64, error) { - ret := _m.Called(ctx, id, subjectType) +// CountSubjects provides a mock function for the type IndexRepo +func (_mock *IndexRepo) CountSubjects(ctx context.Context, id model.IndexID, subjectType model.SubjectType) (int64, error) { + ret := _mock.Called(ctx, id, subjectType) + + if len(ret) == 0 { + panic("no return value specified for CountSubjects") + } var r0 int64 var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint8) (int64, error)); ok { - return rf(ctx, id, subjectType) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.IndexID, model.SubjectType) (int64, error)); ok { + return returnFunc(ctx, id, subjectType) } - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint8) int64); ok { - r0 = rf(ctx, id, subjectType) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.IndexID, model.SubjectType) int64); ok { + r0 = returnFunc(ctx, id, subjectType) } else { r0 = ret.Get(0).(int64) } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, uint8) error); ok { - r1 = rf(ctx, id, subjectType) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.IndexID, model.SubjectType) error); ok { + r1 = returnFunc(ctx, id, subjectType) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -157,40 +221,59 @@ type IndexRepo_CountSubjects_Call struct { // CountSubjects is a helper method to define mock.On call // - ctx context.Context -// - id uint32 -// - subjectType uint8 +// - id model.IndexID +// - subjectType model.SubjectType func (_e *IndexRepo_Expecter) CountSubjects(ctx interface{}, id interface{}, subjectType interface{}) *IndexRepo_CountSubjects_Call { return &IndexRepo_CountSubjects_Call{Call: _e.mock.On("CountSubjects", ctx, id, subjectType)} } -func (_c *IndexRepo_CountSubjects_Call) Run(run func(ctx context.Context, id uint32, subjectType uint8)) *IndexRepo_CountSubjects_Call { +func (_c *IndexRepo_CountSubjects_Call) Run(run func(ctx context.Context, id model.IndexID, subjectType model.SubjectType)) *IndexRepo_CountSubjects_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(uint8)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.IndexID + if args[1] != nil { + arg1 = args[1].(model.IndexID) + } + var arg2 model.SubjectType + if args[2] != nil { + arg2 = args[2].(model.SubjectType) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } -func (_c *IndexRepo_CountSubjects_Call) Return(_a0 int64, _a1 error) *IndexRepo_CountSubjects_Call { - _c.Call.Return(_a0, _a1) +func (_c *IndexRepo_CountSubjects_Call) Return(n int64, err error) *IndexRepo_CountSubjects_Call { + _c.Call.Return(n, err) return _c } -func (_c *IndexRepo_CountSubjects_Call) RunAndReturn(run func(context.Context, uint32, uint8) (int64, error)) *IndexRepo_CountSubjects_Call { +func (_c *IndexRepo_CountSubjects_Call) RunAndReturn(run func(ctx context.Context, id model.IndexID, subjectType model.SubjectType) (int64, error)) *IndexRepo_CountSubjects_Call { _c.Call.Return(run) return _c } -// Delete provides a mock function with given fields: ctx, id -func (_m *IndexRepo) Delete(ctx context.Context, id uint32) error { - ret := _m.Called(ctx, id) +// Delete provides a mock function for the type IndexRepo +func (_mock *IndexRepo) Delete(ctx context.Context, id model.IndexID) error { + ret := _mock.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for Delete") + } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) error); ok { - r0 = rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.IndexID) error); ok { + r0 = returnFunc(ctx, id) } else { r0 = ret.Error(0) } - return r0 } @@ -201,39 +284,53 @@ type IndexRepo_Delete_Call struct { // Delete is a helper method to define mock.On call // - ctx context.Context -// - id uint32 +// - id model.IndexID func (_e *IndexRepo_Expecter) Delete(ctx interface{}, id interface{}) *IndexRepo_Delete_Call { return &IndexRepo_Delete_Call{Call: _e.mock.On("Delete", ctx, id)} } -func (_c *IndexRepo_Delete_Call) Run(run func(ctx context.Context, id uint32)) *IndexRepo_Delete_Call { +func (_c *IndexRepo_Delete_Call) Run(run func(ctx context.Context, id model.IndexID)) *IndexRepo_Delete_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.IndexID + if args[1] != nil { + arg1 = args[1].(model.IndexID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *IndexRepo_Delete_Call) Return(_a0 error) *IndexRepo_Delete_Call { - _c.Call.Return(_a0) +func (_c *IndexRepo_Delete_Call) Return(err error) *IndexRepo_Delete_Call { + _c.Call.Return(err) return _c } -func (_c *IndexRepo_Delete_Call) RunAndReturn(run func(context.Context, uint32) error) *IndexRepo_Delete_Call { +func (_c *IndexRepo_Delete_Call) RunAndReturn(run func(ctx context.Context, id model.IndexID) error) *IndexRepo_Delete_Call { _c.Call.Return(run) return _c } -// DeleteIndexCollect provides a mock function with given fields: ctx, id, uid -func (_m *IndexRepo) DeleteIndexCollect(ctx context.Context, id uint32, uid uint32) error { - ret := _m.Called(ctx, id, uid) +// DeleteIndexCollect provides a mock function for the type IndexRepo +func (_mock *IndexRepo) DeleteIndexCollect(ctx context.Context, id model.IndexID, uid model.UserID) error { + ret := _mock.Called(ctx, id, uid) + + if len(ret) == 0 { + panic("no return value specified for DeleteIndexCollect") + } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint32) error); ok { - r0 = rf(ctx, id, uid) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.IndexID, model.UserID) error); ok { + r0 = returnFunc(ctx, id, uid) } else { r0 = ret.Error(0) } - return r0 } @@ -244,40 +341,59 @@ type IndexRepo_DeleteIndexCollect_Call struct { // DeleteIndexCollect is a helper method to define mock.On call // - ctx context.Context -// - id uint32 -// - uid uint32 +// - id model.IndexID +// - uid model.UserID func (_e *IndexRepo_Expecter) DeleteIndexCollect(ctx interface{}, id interface{}, uid interface{}) *IndexRepo_DeleteIndexCollect_Call { return &IndexRepo_DeleteIndexCollect_Call{Call: _e.mock.On("DeleteIndexCollect", ctx, id, uid)} } -func (_c *IndexRepo_DeleteIndexCollect_Call) Run(run func(ctx context.Context, id uint32, uid uint32)) *IndexRepo_DeleteIndexCollect_Call { +func (_c *IndexRepo_DeleteIndexCollect_Call) Run(run func(ctx context.Context, id model.IndexID, uid model.UserID)) *IndexRepo_DeleteIndexCollect_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.IndexID + if args[1] != nil { + arg1 = args[1].(model.IndexID) + } + var arg2 model.UserID + if args[2] != nil { + arg2 = args[2].(model.UserID) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } -func (_c *IndexRepo_DeleteIndexCollect_Call) Return(_a0 error) *IndexRepo_DeleteIndexCollect_Call { - _c.Call.Return(_a0) +func (_c *IndexRepo_DeleteIndexCollect_Call) Return(err error) *IndexRepo_DeleteIndexCollect_Call { + _c.Call.Return(err) return _c } -func (_c *IndexRepo_DeleteIndexCollect_Call) RunAndReturn(run func(context.Context, uint32, uint32) error) *IndexRepo_DeleteIndexCollect_Call { +func (_c *IndexRepo_DeleteIndexCollect_Call) RunAndReturn(run func(ctx context.Context, id model.IndexID, uid model.UserID) error) *IndexRepo_DeleteIndexCollect_Call { _c.Call.Return(run) return _c } -// DeleteIndexSubject provides a mock function with given fields: ctx, id, subjectID -func (_m *IndexRepo) DeleteIndexSubject(ctx context.Context, id uint32, subjectID uint32) error { - ret := _m.Called(ctx, id, subjectID) +// DeleteIndexSubject provides a mock function for the type IndexRepo +func (_mock *IndexRepo) DeleteIndexSubject(ctx context.Context, id model.IndexID, subjectID model.SubjectID) error { + ret := _mock.Called(ctx, id, subjectID) + + if len(ret) == 0 { + panic("no return value specified for DeleteIndexSubject") + } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint32) error); ok { - r0 = rf(ctx, id, subjectID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.IndexID, model.SubjectID) error); ok { + r0 = returnFunc(ctx, id, subjectID) } else { r0 = ret.Error(0) } - return r0 } @@ -288,50 +404,68 @@ type IndexRepo_DeleteIndexSubject_Call struct { // DeleteIndexSubject is a helper method to define mock.On call // - ctx context.Context -// - id uint32 -// - subjectID uint32 +// - id model.IndexID +// - subjectID model.SubjectID func (_e *IndexRepo_Expecter) DeleteIndexSubject(ctx interface{}, id interface{}, subjectID interface{}) *IndexRepo_DeleteIndexSubject_Call { return &IndexRepo_DeleteIndexSubject_Call{Call: _e.mock.On("DeleteIndexSubject", ctx, id, subjectID)} } -func (_c *IndexRepo_DeleteIndexSubject_Call) Run(run func(ctx context.Context, id uint32, subjectID uint32)) *IndexRepo_DeleteIndexSubject_Call { +func (_c *IndexRepo_DeleteIndexSubject_Call) Run(run func(ctx context.Context, id model.IndexID, subjectID model.SubjectID)) *IndexRepo_DeleteIndexSubject_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.IndexID + if args[1] != nil { + arg1 = args[1].(model.IndexID) + } + var arg2 model.SubjectID + if args[2] != nil { + arg2 = args[2].(model.SubjectID) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } -func (_c *IndexRepo_DeleteIndexSubject_Call) Return(_a0 error) *IndexRepo_DeleteIndexSubject_Call { - _c.Call.Return(_a0) +func (_c *IndexRepo_DeleteIndexSubject_Call) Return(err error) *IndexRepo_DeleteIndexSubject_Call { + _c.Call.Return(err) return _c } -func (_c *IndexRepo_DeleteIndexSubject_Call) RunAndReturn(run func(context.Context, uint32, uint32) error) *IndexRepo_DeleteIndexSubject_Call { +func (_c *IndexRepo_DeleteIndexSubject_Call) RunAndReturn(run func(ctx context.Context, id model.IndexID, subjectID model.SubjectID) error) *IndexRepo_DeleteIndexSubject_Call { _c.Call.Return(run) return _c } -// Get provides a mock function with given fields: ctx, id -func (_m *IndexRepo) Get(ctx context.Context, id uint32) (model.Index, error) { - ret := _m.Called(ctx, id) +// Get provides a mock function for the type IndexRepo +func (_mock *IndexRepo) Get(ctx context.Context, id model.IndexID) (model.Index, error) { + ret := _mock.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for Get") + } var r0 model.Index var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (model.Index, error)); ok { - return rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.IndexID) (model.Index, error)); ok { + return returnFunc(ctx, id) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) model.Index); ok { - r0 = rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.IndexID) model.Index); ok { + r0 = returnFunc(ctx, id) } else { r0 = ret.Get(0).(model.Index) } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, id) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.IndexID) error); ok { + r1 = returnFunc(ctx, id) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -342,51 +476,64 @@ type IndexRepo_Get_Call struct { // Get is a helper method to define mock.On call // - ctx context.Context -// - id uint32 +// - id model.IndexID func (_e *IndexRepo_Expecter) Get(ctx interface{}, id interface{}) *IndexRepo_Get_Call { return &IndexRepo_Get_Call{Call: _e.mock.On("Get", ctx, id)} } -func (_c *IndexRepo_Get_Call) Run(run func(ctx context.Context, id uint32)) *IndexRepo_Get_Call { +func (_c *IndexRepo_Get_Call) Run(run func(ctx context.Context, id model.IndexID)) *IndexRepo_Get_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.IndexID + if args[1] != nil { + arg1 = args[1].(model.IndexID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *IndexRepo_Get_Call) Return(_a0 model.Index, _a1 error) *IndexRepo_Get_Call { - _c.Call.Return(_a0, _a1) +func (_c *IndexRepo_Get_Call) Return(index1 model.Index, err error) *IndexRepo_Get_Call { + _c.Call.Return(index1, err) return _c } -func (_c *IndexRepo_Get_Call) RunAndReturn(run func(context.Context, uint32) (model.Index, error)) *IndexRepo_Get_Call { +func (_c *IndexRepo_Get_Call) RunAndReturn(run func(ctx context.Context, id model.IndexID) (model.Index, error)) *IndexRepo_Get_Call { _c.Call.Return(run) return _c } -// GetIndexCollect provides a mock function with given fields: ctx, id, uid -func (_m *IndexRepo) GetIndexCollect(ctx context.Context, id uint32, uid uint32) (*index.IndexCollect, error) { - ret := _m.Called(ctx, id, uid) +// GetIndexCollect provides a mock function for the type IndexRepo +func (_mock *IndexRepo) GetIndexCollect(ctx context.Context, id model.IndexID, uid model.UserID) (*index.IndexCollect, error) { + ret := _mock.Called(ctx, id, uid) + + if len(ret) == 0 { + panic("no return value specified for GetIndexCollect") + } var r0 *index.IndexCollect var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint32) (*index.IndexCollect, error)); ok { - return rf(ctx, id, uid) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.IndexID, model.UserID) (*index.IndexCollect, error)); ok { + return returnFunc(ctx, id, uid) } - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint32) *index.IndexCollect); ok { - r0 = rf(ctx, id, uid) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.IndexID, model.UserID) *index.IndexCollect); ok { + r0 = returnFunc(ctx, id, uid) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*index.IndexCollect) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, uint32) error); ok { - r1 = rf(ctx, id, uid) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.IndexID, model.UserID) error); ok { + r1 = returnFunc(ctx, id, uid) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -397,52 +544,70 @@ type IndexRepo_GetIndexCollect_Call struct { // GetIndexCollect is a helper method to define mock.On call // - ctx context.Context -// - id uint32 -// - uid uint32 +// - id model.IndexID +// - uid model.UserID func (_e *IndexRepo_Expecter) GetIndexCollect(ctx interface{}, id interface{}, uid interface{}) *IndexRepo_GetIndexCollect_Call { return &IndexRepo_GetIndexCollect_Call{Call: _e.mock.On("GetIndexCollect", ctx, id, uid)} } -func (_c *IndexRepo_GetIndexCollect_Call) Run(run func(ctx context.Context, id uint32, uid uint32)) *IndexRepo_GetIndexCollect_Call { +func (_c *IndexRepo_GetIndexCollect_Call) Run(run func(ctx context.Context, id model.IndexID, uid model.UserID)) *IndexRepo_GetIndexCollect_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.IndexID + if args[1] != nil { + arg1 = args[1].(model.IndexID) + } + var arg2 model.UserID + if args[2] != nil { + arg2 = args[2].(model.UserID) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } -func (_c *IndexRepo_GetIndexCollect_Call) Return(_a0 *index.IndexCollect, _a1 error) *IndexRepo_GetIndexCollect_Call { - _c.Call.Return(_a0, _a1) +func (_c *IndexRepo_GetIndexCollect_Call) Return(indexCollect *index.IndexCollect, err error) *IndexRepo_GetIndexCollect_Call { + _c.Call.Return(indexCollect, err) return _c } -func (_c *IndexRepo_GetIndexCollect_Call) RunAndReturn(run func(context.Context, uint32, uint32) (*index.IndexCollect, error)) *IndexRepo_GetIndexCollect_Call { +func (_c *IndexRepo_GetIndexCollect_Call) RunAndReturn(run func(ctx context.Context, id model.IndexID, uid model.UserID) (*index.IndexCollect, error)) *IndexRepo_GetIndexCollect_Call { _c.Call.Return(run) return _c } -// ListSubjects provides a mock function with given fields: ctx, id, subjectType, limit, offset -func (_m *IndexRepo) ListSubjects(ctx context.Context, id uint32, subjectType uint8, limit int, offset int) ([]index.Subject, error) { - ret := _m.Called(ctx, id, subjectType, limit, offset) +// ListSubjects provides a mock function for the type IndexRepo +func (_mock *IndexRepo) ListSubjects(ctx context.Context, id model.IndexID, subjectType model.SubjectType, limit int, offset int) ([]index.Subject, error) { + ret := _mock.Called(ctx, id, subjectType, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for ListSubjects") + } var r0 []index.Subject var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint8, int, int) ([]index.Subject, error)); ok { - return rf(ctx, id, subjectType, limit, offset) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.IndexID, model.SubjectType, int, int) ([]index.Subject, error)); ok { + return returnFunc(ctx, id, subjectType, limit, offset) } - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint8, int, int) []index.Subject); ok { - r0 = rf(ctx, id, subjectType, limit, offset) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.IndexID, model.SubjectType, int, int) []index.Subject); ok { + r0 = returnFunc(ctx, id, subjectType, limit, offset) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]index.Subject) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, uint8, int, int) error); ok { - r1 = rf(ctx, id, subjectType, limit, offset) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.IndexID, model.SubjectType, int, int) error); ok { + r1 = returnFunc(ctx, id, subjectType, limit, offset) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -453,42 +618,71 @@ type IndexRepo_ListSubjects_Call struct { // ListSubjects is a helper method to define mock.On call // - ctx context.Context -// - id uint32 -// - subjectType uint8 +// - id model.IndexID +// - subjectType model.SubjectType // - limit int // - offset int func (_e *IndexRepo_Expecter) ListSubjects(ctx interface{}, id interface{}, subjectType interface{}, limit interface{}, offset interface{}) *IndexRepo_ListSubjects_Call { return &IndexRepo_ListSubjects_Call{Call: _e.mock.On("ListSubjects", ctx, id, subjectType, limit, offset)} } -func (_c *IndexRepo_ListSubjects_Call) Run(run func(ctx context.Context, id uint32, subjectType uint8, limit int, offset int)) *IndexRepo_ListSubjects_Call { +func (_c *IndexRepo_ListSubjects_Call) Run(run func(ctx context.Context, id model.IndexID, subjectType model.SubjectType, limit int, offset int)) *IndexRepo_ListSubjects_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(uint8), args[3].(int), args[4].(int)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.IndexID + if args[1] != nil { + arg1 = args[1].(model.IndexID) + } + var arg2 model.SubjectType + if args[2] != nil { + arg2 = args[2].(model.SubjectType) + } + var arg3 int + if args[3] != nil { + arg3 = args[3].(int) + } + var arg4 int + if args[4] != nil { + arg4 = args[4].(int) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + ) }) return _c } -func (_c *IndexRepo_ListSubjects_Call) Return(_a0 []index.Subject, _a1 error) *IndexRepo_ListSubjects_Call { - _c.Call.Return(_a0, _a1) +func (_c *IndexRepo_ListSubjects_Call) Return(subjects []index.Subject, err error) *IndexRepo_ListSubjects_Call { + _c.Call.Return(subjects, err) return _c } -func (_c *IndexRepo_ListSubjects_Call) RunAndReturn(run func(context.Context, uint32, uint8, int, int) ([]index.Subject, error)) *IndexRepo_ListSubjects_Call { +func (_c *IndexRepo_ListSubjects_Call) RunAndReturn(run func(ctx context.Context, id model.IndexID, subjectType model.SubjectType, limit int, offset int) ([]index.Subject, error)) *IndexRepo_ListSubjects_Call { _c.Call.Return(run) return _c } -// New provides a mock function with given fields: ctx, i -func (_m *IndexRepo) New(ctx context.Context, i *model.Index) error { - ret := _m.Called(ctx, i) +// New provides a mock function for the type IndexRepo +func (_mock *IndexRepo) New(ctx context.Context, i *model.Index) error { + ret := _mock.Called(ctx, i) + + if len(ret) == 0 { + panic("no return value specified for New") + } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *model.Index) error); ok { - r0 = rf(ctx, i) + if returnFunc, ok := ret.Get(0).(func(context.Context, *model.Index) error); ok { + r0 = returnFunc(ctx, i) } else { r0 = ret.Error(0) } - return r0 } @@ -506,32 +700,46 @@ func (_e *IndexRepo_Expecter) New(ctx interface{}, i interface{}) *IndexRepo_New func (_c *IndexRepo_New_Call) Run(run func(ctx context.Context, i *model.Index)) *IndexRepo_New_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*model.Index)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 *model.Index + if args[1] != nil { + arg1 = args[1].(*model.Index) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *IndexRepo_New_Call) Return(_a0 error) *IndexRepo_New_Call { - _c.Call.Return(_a0) +func (_c *IndexRepo_New_Call) Return(err error) *IndexRepo_New_Call { + _c.Call.Return(err) return _c } -func (_c *IndexRepo_New_Call) RunAndReturn(run func(context.Context, *model.Index) error) *IndexRepo_New_Call { +func (_c *IndexRepo_New_Call) RunAndReturn(run func(ctx context.Context, i *model.Index) error) *IndexRepo_New_Call { _c.Call.Return(run) return _c } -// Update provides a mock function with given fields: ctx, id, title, desc -func (_m *IndexRepo) Update(ctx context.Context, id uint32, title string, desc string) error { - ret := _m.Called(ctx, id, title, desc) +// Update provides a mock function for the type IndexRepo +func (_mock *IndexRepo) Update(ctx context.Context, id model.IndexID, title string, desc string) error { + ret := _mock.Called(ctx, id, title, desc) + + if len(ret) == 0 { + panic("no return value specified for Update") + } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, string, string) error); ok { - r0 = rf(ctx, id, title, desc) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.IndexID, string, string) error); ok { + r0 = returnFunc(ctx, id, title, desc) } else { r0 = ret.Error(0) } - return r0 } @@ -542,41 +750,47 @@ type IndexRepo_Update_Call struct { // Update is a helper method to define mock.On call // - ctx context.Context -// - id uint32 +// - id model.IndexID // - title string // - desc string func (_e *IndexRepo_Expecter) Update(ctx interface{}, id interface{}, title interface{}, desc interface{}) *IndexRepo_Update_Call { return &IndexRepo_Update_Call{Call: _e.mock.On("Update", ctx, id, title, desc)} } -func (_c *IndexRepo_Update_Call) Run(run func(ctx context.Context, id uint32, title string, desc string)) *IndexRepo_Update_Call { +func (_c *IndexRepo_Update_Call) Run(run func(ctx context.Context, id model.IndexID, title string, desc string)) *IndexRepo_Update_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(string), args[3].(string)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.IndexID + if args[1] != nil { + arg1 = args[1].(model.IndexID) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + ) }) return _c } -func (_c *IndexRepo_Update_Call) Return(_a0 error) *IndexRepo_Update_Call { - _c.Call.Return(_a0) +func (_c *IndexRepo_Update_Call) Return(err error) *IndexRepo_Update_Call { + _c.Call.Return(err) return _c } -func (_c *IndexRepo_Update_Call) RunAndReturn(run func(context.Context, uint32, string, string) error) *IndexRepo_Update_Call { +func (_c *IndexRepo_Update_Call) RunAndReturn(run func(ctx context.Context, id model.IndexID, title string, desc string) error) *IndexRepo_Update_Call { _c.Call.Return(run) return _c } - -type mockConstructorTestingTNewIndexRepo interface { - mock.TestingT - Cleanup(func()) -} - -// NewIndexRepo creates a new instance of IndexRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewIndexRepo(t mockConstructorTestingTNewIndexRepo) *IndexRepo { - mock := &IndexRepo{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/mocks/NotificationRepo.go b/internal/mocks/NotificationRepo.go deleted file mode 100644 index 342a39f36..000000000 --- a/internal/mocks/NotificationRepo.go +++ /dev/null @@ -1,90 +0,0 @@ -// Code generated by mockery v2.23.1. DO NOT EDIT. - -package mocks - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" -) - -// NotificationRepo is an autogenerated mock type for the Repo type -type NotificationRepo struct { - mock.Mock -} - -type NotificationRepo_Expecter struct { - mock *mock.Mock -} - -func (_m *NotificationRepo) EXPECT() *NotificationRepo_Expecter { - return &NotificationRepo_Expecter{mock: &_m.Mock} -} - -// Count provides a mock function with given fields: ctx, userID -func (_m *NotificationRepo) Count(ctx context.Context, userID uint32) (int64, error) { - ret := _m.Called(ctx, userID) - - var r0 int64 - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (int64, error)); ok { - return rf(ctx, userID) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32) int64); ok { - r0 = rf(ctx, userID) - } else { - r0 = ret.Get(0).(int64) - } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, userID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// NotificationRepo_Count_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Count' -type NotificationRepo_Count_Call struct { - *mock.Call -} - -// Count is a helper method to define mock.On call -// - ctx context.Context -// - userID uint32 -func (_e *NotificationRepo_Expecter) Count(ctx interface{}, userID interface{}) *NotificationRepo_Count_Call { - return &NotificationRepo_Count_Call{Call: _e.mock.On("Count", ctx, userID)} -} - -func (_c *NotificationRepo_Count_Call) Run(run func(ctx context.Context, userID uint32)) *NotificationRepo_Count_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) - }) - return _c -} - -func (_c *NotificationRepo_Count_Call) Return(_a0 int64, _a1 error) *NotificationRepo_Count_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *NotificationRepo_Count_Call) RunAndReturn(run func(context.Context, uint32) (int64, error)) *NotificationRepo_Count_Call { - _c.Call.Return(run) - return _c -} - -type mockConstructorTestingTNewNotificationRepo interface { - mock.TestingT - Cleanup(func()) -} - -// NewNotificationRepo creates a new instance of NotificationRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewNotificationRepo(t mockConstructorTestingTNewNotificationRepo) *NotificationRepo { - mock := &NotificationRepo{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/mocks/PersonRepo.go b/internal/mocks/PersonRepo.go index 917e89437..a6f4a1dd7 100644 --- a/internal/mocks/PersonRepo.go +++ b/internal/mocks/PersonRepo.go @@ -1,16 +1,31 @@ -// Code generated by mockery v2.23.1. DO NOT EDIT. +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify package mocks import ( - context "context" + "context" - domain "github.com/bangumi/server/domain" + "github.com/bangumi/server/domain" + "github.com/bangumi/server/internal/model" mock "github.com/stretchr/testify/mock" - - model "github.com/bangumi/server/internal/model" ) +// NewPersonRepo creates a new instance of PersonRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewPersonRepo(t interface { + mock.TestingT + Cleanup(func()) +}) *PersonRepo { + mock := &PersonRepo{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + // PersonRepo is an autogenerated mock type for the Repo type type PersonRepo struct { mock.Mock @@ -24,27 +39,29 @@ func (_m *PersonRepo) EXPECT() *PersonRepo_Expecter { return &PersonRepo_Expecter{mock: &_m.Mock} } -// Get provides a mock function with given fields: ctx, id -func (_m *PersonRepo) Get(ctx context.Context, id uint32) (model.Person, error) { - ret := _m.Called(ctx, id) +// Get provides a mock function for the type PersonRepo +func (_mock *PersonRepo) Get(ctx context.Context, id model.PersonID) (model.Person, error) { + ret := _mock.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for Get") + } var r0 model.Person var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (model.Person, error)); ok { - return rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.PersonID) (model.Person, error)); ok { + return returnFunc(ctx, id) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) model.Person); ok { - r0 = rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.PersonID) model.Person); ok { + r0 = returnFunc(ctx, id) } else { r0 = ret.Get(0).(model.Person) } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, id) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.PersonID) error); ok { + r1 = returnFunc(ctx, id) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -55,51 +72,64 @@ type PersonRepo_Get_Call struct { // Get is a helper method to define mock.On call // - ctx context.Context -// - id uint32 +// - id model.PersonID func (_e *PersonRepo_Expecter) Get(ctx interface{}, id interface{}) *PersonRepo_Get_Call { return &PersonRepo_Get_Call{Call: _e.mock.On("Get", ctx, id)} } -func (_c *PersonRepo_Get_Call) Run(run func(ctx context.Context, id uint32)) *PersonRepo_Get_Call { +func (_c *PersonRepo_Get_Call) Run(run func(ctx context.Context, id model.PersonID)) *PersonRepo_Get_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.PersonID + if args[1] != nil { + arg1 = args[1].(model.PersonID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *PersonRepo_Get_Call) Return(_a0 model.Person, _a1 error) *PersonRepo_Get_Call { - _c.Call.Return(_a0, _a1) +func (_c *PersonRepo_Get_Call) Return(person model.Person, err error) *PersonRepo_Get_Call { + _c.Call.Return(person, err) return _c } -func (_c *PersonRepo_Get_Call) RunAndReturn(run func(context.Context, uint32) (model.Person, error)) *PersonRepo_Get_Call { +func (_c *PersonRepo_Get_Call) RunAndReturn(run func(ctx context.Context, id model.PersonID) (model.Person, error)) *PersonRepo_Get_Call { _c.Call.Return(run) return _c } -// GetByIDs provides a mock function with given fields: ctx, ids -func (_m *PersonRepo) GetByIDs(ctx context.Context, ids []uint32) (map[uint32]model.Person, error) { - ret := _m.Called(ctx, ids) +// GetByIDs provides a mock function for the type PersonRepo +func (_mock *PersonRepo) GetByIDs(ctx context.Context, ids []model.PersonID) (map[model.PersonID]model.Person, error) { + ret := _mock.Called(ctx, ids) + + if len(ret) == 0 { + panic("no return value specified for GetByIDs") + } - var r0 map[uint32]model.Person + var r0 map[model.PersonID]model.Person var r1 error - if rf, ok := ret.Get(0).(func(context.Context, []uint32) (map[uint32]model.Person, error)); ok { - return rf(ctx, ids) + if returnFunc, ok := ret.Get(0).(func(context.Context, []model.PersonID) (map[model.PersonID]model.Person, error)); ok { + return returnFunc(ctx, ids) } - if rf, ok := ret.Get(0).(func(context.Context, []uint32) map[uint32]model.Person); ok { - r0 = rf(ctx, ids) + if returnFunc, ok := ret.Get(0).(func(context.Context, []model.PersonID) map[model.PersonID]model.Person); ok { + r0 = returnFunc(ctx, ids) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(map[uint32]model.Person) + r0 = ret.Get(0).(map[model.PersonID]model.Person) } } - - if rf, ok := ret.Get(1).(func(context.Context, []uint32) error); ok { - r1 = rf(ctx, ids) + if returnFunc, ok := ret.Get(1).(func(context.Context, []model.PersonID) error); ok { + r1 = returnFunc(ctx, ids) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -110,51 +140,64 @@ type PersonRepo_GetByIDs_Call struct { // GetByIDs is a helper method to define mock.On call // - ctx context.Context -// - ids []uint32 +// - ids []model.PersonID func (_e *PersonRepo_Expecter) GetByIDs(ctx interface{}, ids interface{}) *PersonRepo_GetByIDs_Call { return &PersonRepo_GetByIDs_Call{Call: _e.mock.On("GetByIDs", ctx, ids)} } -func (_c *PersonRepo_GetByIDs_Call) Run(run func(ctx context.Context, ids []uint32)) *PersonRepo_GetByIDs_Call { +func (_c *PersonRepo_GetByIDs_Call) Run(run func(ctx context.Context, ids []model.PersonID)) *PersonRepo_GetByIDs_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].([]uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []model.PersonID + if args[1] != nil { + arg1 = args[1].([]model.PersonID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *PersonRepo_GetByIDs_Call) Return(_a0 map[uint32]model.Person, _a1 error) *PersonRepo_GetByIDs_Call { - _c.Call.Return(_a0, _a1) +func (_c *PersonRepo_GetByIDs_Call) Return(vToPerson map[model.PersonID]model.Person, err error) *PersonRepo_GetByIDs_Call { + _c.Call.Return(vToPerson, err) return _c } -func (_c *PersonRepo_GetByIDs_Call) RunAndReturn(run func(context.Context, []uint32) (map[uint32]model.Person, error)) *PersonRepo_GetByIDs_Call { +func (_c *PersonRepo_GetByIDs_Call) RunAndReturn(run func(ctx context.Context, ids []model.PersonID) (map[model.PersonID]model.Person, error)) *PersonRepo_GetByIDs_Call { _c.Call.Return(run) return _c } -// GetCharacterRelated provides a mock function with given fields: ctx, subjectID -func (_m *PersonRepo) GetCharacterRelated(ctx context.Context, subjectID uint32) ([]domain.PersonCharacterRelation, error) { - ret := _m.Called(ctx, subjectID) +// GetCharacterRelated provides a mock function for the type PersonRepo +func (_mock *PersonRepo) GetCharacterRelated(ctx context.Context, subjectID model.CharacterID) ([]domain.PersonCharacterRelation, error) { + ret := _mock.Called(ctx, subjectID) + + if len(ret) == 0 { + panic("no return value specified for GetCharacterRelated") + } var r0 []domain.PersonCharacterRelation var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) ([]domain.PersonCharacterRelation, error)); ok { - return rf(ctx, subjectID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.CharacterID) ([]domain.PersonCharacterRelation, error)); ok { + return returnFunc(ctx, subjectID) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) []domain.PersonCharacterRelation); ok { - r0 = rf(ctx, subjectID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.CharacterID) []domain.PersonCharacterRelation); ok { + r0 = returnFunc(ctx, subjectID) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]domain.PersonCharacterRelation) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, subjectID) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.CharacterID) error); ok { + r1 = returnFunc(ctx, subjectID) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -165,51 +208,64 @@ type PersonRepo_GetCharacterRelated_Call struct { // GetCharacterRelated is a helper method to define mock.On call // - ctx context.Context -// - subjectID uint32 +// - subjectID model.CharacterID func (_e *PersonRepo_Expecter) GetCharacterRelated(ctx interface{}, subjectID interface{}) *PersonRepo_GetCharacterRelated_Call { return &PersonRepo_GetCharacterRelated_Call{Call: _e.mock.On("GetCharacterRelated", ctx, subjectID)} } -func (_c *PersonRepo_GetCharacterRelated_Call) Run(run func(ctx context.Context, subjectID uint32)) *PersonRepo_GetCharacterRelated_Call { +func (_c *PersonRepo_GetCharacterRelated_Call) Run(run func(ctx context.Context, subjectID model.CharacterID)) *PersonRepo_GetCharacterRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.CharacterID + if args[1] != nil { + arg1 = args[1].(model.CharacterID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *PersonRepo_GetCharacterRelated_Call) Return(_a0 []domain.PersonCharacterRelation, _a1 error) *PersonRepo_GetCharacterRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *PersonRepo_GetCharacterRelated_Call) Return(personCharacterRelations []domain.PersonCharacterRelation, err error) *PersonRepo_GetCharacterRelated_Call { + _c.Call.Return(personCharacterRelations, err) return _c } -func (_c *PersonRepo_GetCharacterRelated_Call) RunAndReturn(run func(context.Context, uint32) ([]domain.PersonCharacterRelation, error)) *PersonRepo_GetCharacterRelated_Call { +func (_c *PersonRepo_GetCharacterRelated_Call) RunAndReturn(run func(ctx context.Context, subjectID model.CharacterID) ([]domain.PersonCharacterRelation, error)) *PersonRepo_GetCharacterRelated_Call { _c.Call.Return(run) return _c } -// GetSubjectRelated provides a mock function with given fields: ctx, subjectID -func (_m *PersonRepo) GetSubjectRelated(ctx context.Context, subjectID uint32) ([]domain.SubjectPersonRelation, error) { - ret := _m.Called(ctx, subjectID) +// GetSubjectRelated provides a mock function for the type PersonRepo +func (_mock *PersonRepo) GetSubjectRelated(ctx context.Context, subjectID model.SubjectID) ([]domain.SubjectPersonRelation, error) { + ret := _mock.Called(ctx, subjectID) + + if len(ret) == 0 { + panic("no return value specified for GetSubjectRelated") + } var r0 []domain.SubjectPersonRelation var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) ([]domain.SubjectPersonRelation, error)); ok { - return rf(ctx, subjectID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID) ([]domain.SubjectPersonRelation, error)); ok { + return returnFunc(ctx, subjectID) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) []domain.SubjectPersonRelation); ok { - r0 = rf(ctx, subjectID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID) []domain.SubjectPersonRelation); ok { + r0 = returnFunc(ctx, subjectID) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]domain.SubjectPersonRelation) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, subjectID) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.SubjectID) error); ok { + r1 = returnFunc(ctx, subjectID) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -220,39 +276,35 @@ type PersonRepo_GetSubjectRelated_Call struct { // GetSubjectRelated is a helper method to define mock.On call // - ctx context.Context -// - subjectID uint32 +// - subjectID model.SubjectID func (_e *PersonRepo_Expecter) GetSubjectRelated(ctx interface{}, subjectID interface{}) *PersonRepo_GetSubjectRelated_Call { return &PersonRepo_GetSubjectRelated_Call{Call: _e.mock.On("GetSubjectRelated", ctx, subjectID)} } -func (_c *PersonRepo_GetSubjectRelated_Call) Run(run func(ctx context.Context, subjectID uint32)) *PersonRepo_GetSubjectRelated_Call { +func (_c *PersonRepo_GetSubjectRelated_Call) Run(run func(ctx context.Context, subjectID model.SubjectID)) *PersonRepo_GetSubjectRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.SubjectID + if args[1] != nil { + arg1 = args[1].(model.SubjectID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *PersonRepo_GetSubjectRelated_Call) Return(_a0 []domain.SubjectPersonRelation, _a1 error) *PersonRepo_GetSubjectRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *PersonRepo_GetSubjectRelated_Call) Return(subjectPersonRelations []domain.SubjectPersonRelation, err error) *PersonRepo_GetSubjectRelated_Call { + _c.Call.Return(subjectPersonRelations, err) return _c } -func (_c *PersonRepo_GetSubjectRelated_Call) RunAndReturn(run func(context.Context, uint32) ([]domain.SubjectPersonRelation, error)) *PersonRepo_GetSubjectRelated_Call { +func (_c *PersonRepo_GetSubjectRelated_Call) RunAndReturn(run func(ctx context.Context, subjectID model.SubjectID) ([]domain.SubjectPersonRelation, error)) *PersonRepo_GetSubjectRelated_Call { _c.Call.Return(run) return _c } - -type mockConstructorTestingTNewPersonRepo interface { - mock.TestingT - Cleanup(func()) -} - -// NewPersonRepo creates a new instance of PersonRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewPersonRepo(t mockConstructorTestingTNewPersonRepo) *PersonRepo { - mock := &PersonRepo{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/mocks/PersonService.go b/internal/mocks/PersonService.go index 800f72495..05fa750ff 100644 --- a/internal/mocks/PersonService.go +++ b/internal/mocks/PersonService.go @@ -1,17 +1,31 @@ -// Code generated by mockery v2.23.1. DO NOT EDIT. +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify package mocks import ( - context "context" + "context" - domain "github.com/bangumi/server/domain" + "github.com/bangumi/server/internal/model" mock "github.com/stretchr/testify/mock" - - model "github.com/bangumi/server/internal/model" ) -// PersonService is an autogenerated mock type for the Repo type +// NewPersonService creates a new instance of PersonService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewPersonService(t interface { + mock.TestingT + Cleanup(func()) +}) *PersonService { + mock := &PersonService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// PersonService is an autogenerated mock type for the Service type type PersonService struct { mock.Mock } @@ -24,27 +38,29 @@ func (_m *PersonService) EXPECT() *PersonService_Expecter { return &PersonService_Expecter{mock: &_m.Mock} } -// Get provides a mock function with given fields: ctx, id -func (_m *PersonService) Get(ctx context.Context, id uint32) (model.Person, error) { - ret := _m.Called(ctx, id) +// Get provides a mock function for the type PersonService +func (_mock *PersonService) Get(ctx context.Context, id model.PersonID) (model.Person, error) { + ret := _mock.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for Get") + } var r0 model.Person var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (model.Person, error)); ok { - return rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.PersonID) (model.Person, error)); ok { + return returnFunc(ctx, id) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) model.Person); ok { - r0 = rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.PersonID) model.Person); ok { + r0 = returnFunc(ctx, id) } else { r0 = ret.Get(0).(model.Person) } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, id) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.PersonID) error); ok { + r1 = returnFunc(ctx, id) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -55,106 +71,64 @@ type PersonService_Get_Call struct { // Get is a helper method to define mock.On call // - ctx context.Context -// - id uint32 +// - id model.PersonID func (_e *PersonService_Expecter) Get(ctx interface{}, id interface{}) *PersonService_Get_Call { return &PersonService_Get_Call{Call: _e.mock.On("Get", ctx, id)} } -func (_c *PersonService_Get_Call) Run(run func(ctx context.Context, id uint32)) *PersonService_Get_Call { +func (_c *PersonService_Get_Call) Run(run func(ctx context.Context, id model.PersonID)) *PersonService_Get_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.PersonID + if args[1] != nil { + arg1 = args[1].(model.PersonID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *PersonService_Get_Call) Return(_a0 model.Person, _a1 error) *PersonService_Get_Call { - _c.Call.Return(_a0, _a1) +func (_c *PersonService_Get_Call) Return(person model.Person, err error) *PersonService_Get_Call { + _c.Call.Return(person, err) return _c } -func (_c *PersonService_Get_Call) RunAndReturn(run func(context.Context, uint32) (model.Person, error)) *PersonService_Get_Call { +func (_c *PersonService_Get_Call) RunAndReturn(run func(ctx context.Context, id model.PersonID) (model.Person, error)) *PersonService_Get_Call { _c.Call.Return(run) return _c } -// GetByIDs provides a mock function with given fields: ctx, ids -func (_m *PersonService) GetByIDs(ctx context.Context, ids []uint32) (map[uint32]model.Person, error) { - ret := _m.Called(ctx, ids) +// GetCharacterRelated provides a mock function for the type PersonService +func (_mock *PersonService) GetCharacterRelated(ctx context.Context, characterID model.CharacterID) ([]model.PersonCharacterRelation, error) { + ret := _mock.Called(ctx, characterID) - var r0 map[uint32]model.Person - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, []uint32) (map[uint32]model.Person, error)); ok { - return rf(ctx, ids) - } - if rf, ok := ret.Get(0).(func(context.Context, []uint32) map[uint32]model.Person); ok { - r0 = rf(ctx, ids) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(map[uint32]model.Person) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, []uint32) error); ok { - r1 = rf(ctx, ids) - } else { - r1 = ret.Error(1) + if len(ret) == 0 { + panic("no return value specified for GetCharacterRelated") } - return r0, r1 -} - -// PersonService_GetByIDs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByIDs' -type PersonService_GetByIDs_Call struct { - *mock.Call -} - -// GetByIDs is a helper method to define mock.On call -// - ctx context.Context -// - ids []uint32 -func (_e *PersonService_Expecter) GetByIDs(ctx interface{}, ids interface{}) *PersonService_GetByIDs_Call { - return &PersonService_GetByIDs_Call{Call: _e.mock.On("GetByIDs", ctx, ids)} -} - -func (_c *PersonService_GetByIDs_Call) Run(run func(ctx context.Context, ids []uint32)) *PersonService_GetByIDs_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].([]uint32)) - }) - return _c -} - -func (_c *PersonService_GetByIDs_Call) Return(_a0 map[uint32]model.Person, _a1 error) *PersonService_GetByIDs_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *PersonService_GetByIDs_Call) RunAndReturn(run func(context.Context, []uint32) (map[uint32]model.Person, error)) *PersonService_GetByIDs_Call { - _c.Call.Return(run) - return _c -} - -// GetCharacterRelated provides a mock function with given fields: ctx, subjectID -func (_m *PersonService) GetCharacterRelated(ctx context.Context, subjectID uint32) ([]domain.PersonCharacterRelation, error) { - ret := _m.Called(ctx, subjectID) - - var r0 []domain.PersonCharacterRelation + var r0 []model.PersonCharacterRelation var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) ([]domain.PersonCharacterRelation, error)); ok { - return rf(ctx, subjectID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.CharacterID) ([]model.PersonCharacterRelation, error)); ok { + return returnFunc(ctx, characterID) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) []domain.PersonCharacterRelation); ok { - r0 = rf(ctx, subjectID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.CharacterID) []model.PersonCharacterRelation); ok { + r0 = returnFunc(ctx, characterID) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]domain.PersonCharacterRelation) + r0 = ret.Get(0).([]model.PersonCharacterRelation) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, subjectID) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.CharacterID) error); ok { + r1 = returnFunc(ctx, characterID) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -165,51 +139,64 @@ type PersonService_GetCharacterRelated_Call struct { // GetCharacterRelated is a helper method to define mock.On call // - ctx context.Context -// - subjectID uint32 -func (_e *PersonService_Expecter) GetCharacterRelated(ctx interface{}, subjectID interface{}) *PersonService_GetCharacterRelated_Call { - return &PersonService_GetCharacterRelated_Call{Call: _e.mock.On("GetCharacterRelated", ctx, subjectID)} +// - characterID model.CharacterID +func (_e *PersonService_Expecter) GetCharacterRelated(ctx interface{}, characterID interface{}) *PersonService_GetCharacterRelated_Call { + return &PersonService_GetCharacterRelated_Call{Call: _e.mock.On("GetCharacterRelated", ctx, characterID)} } -func (_c *PersonService_GetCharacterRelated_Call) Run(run func(ctx context.Context, subjectID uint32)) *PersonService_GetCharacterRelated_Call { +func (_c *PersonService_GetCharacterRelated_Call) Run(run func(ctx context.Context, characterID model.CharacterID)) *PersonService_GetCharacterRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.CharacterID + if args[1] != nil { + arg1 = args[1].(model.CharacterID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *PersonService_GetCharacterRelated_Call) Return(_a0 []domain.PersonCharacterRelation, _a1 error) *PersonService_GetCharacterRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *PersonService_GetCharacterRelated_Call) Return(personCharacterRelations []model.PersonCharacterRelation, err error) *PersonService_GetCharacterRelated_Call { + _c.Call.Return(personCharacterRelations, err) return _c } -func (_c *PersonService_GetCharacterRelated_Call) RunAndReturn(run func(context.Context, uint32) ([]domain.PersonCharacterRelation, error)) *PersonService_GetCharacterRelated_Call { +func (_c *PersonService_GetCharacterRelated_Call) RunAndReturn(run func(ctx context.Context, characterID model.CharacterID) ([]model.PersonCharacterRelation, error)) *PersonService_GetCharacterRelated_Call { _c.Call.Return(run) return _c } -// GetSubjectRelated provides a mock function with given fields: ctx, subjectID -func (_m *PersonService) GetSubjectRelated(ctx context.Context, subjectID uint32) ([]domain.SubjectPersonRelation, error) { - ret := _m.Called(ctx, subjectID) +// GetSubjectRelated provides a mock function for the type PersonService +func (_mock *PersonService) GetSubjectRelated(ctx context.Context, subjectID model.SubjectID) ([]model.SubjectPersonRelation, error) { + ret := _mock.Called(ctx, subjectID) + + if len(ret) == 0 { + panic("no return value specified for GetSubjectRelated") + } - var r0 []domain.SubjectPersonRelation + var r0 []model.SubjectPersonRelation var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) ([]domain.SubjectPersonRelation, error)); ok { - return rf(ctx, subjectID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID) ([]model.SubjectPersonRelation, error)); ok { + return returnFunc(ctx, subjectID) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) []domain.SubjectPersonRelation); ok { - r0 = rf(ctx, subjectID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID) []model.SubjectPersonRelation); ok { + r0 = returnFunc(ctx, subjectID) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]domain.SubjectPersonRelation) + r0 = ret.Get(0).([]model.SubjectPersonRelation) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, subjectID) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.SubjectID) error); ok { + r1 = returnFunc(ctx, subjectID) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -220,39 +207,35 @@ type PersonService_GetSubjectRelated_Call struct { // GetSubjectRelated is a helper method to define mock.On call // - ctx context.Context -// - subjectID uint32 +// - subjectID model.SubjectID func (_e *PersonService_Expecter) GetSubjectRelated(ctx interface{}, subjectID interface{}) *PersonService_GetSubjectRelated_Call { return &PersonService_GetSubjectRelated_Call{Call: _e.mock.On("GetSubjectRelated", ctx, subjectID)} } -func (_c *PersonService_GetSubjectRelated_Call) Run(run func(ctx context.Context, subjectID uint32)) *PersonService_GetSubjectRelated_Call { +func (_c *PersonService_GetSubjectRelated_Call) Run(run func(ctx context.Context, subjectID model.SubjectID)) *PersonService_GetSubjectRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.SubjectID + if args[1] != nil { + arg1 = args[1].(model.SubjectID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *PersonService_GetSubjectRelated_Call) Return(_a0 []domain.SubjectPersonRelation, _a1 error) *PersonService_GetSubjectRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *PersonService_GetSubjectRelated_Call) Return(subjectPersonRelations []model.SubjectPersonRelation, err error) *PersonService_GetSubjectRelated_Call { + _c.Call.Return(subjectPersonRelations, err) return _c } -func (_c *PersonService_GetSubjectRelated_Call) RunAndReturn(run func(context.Context, uint32) ([]domain.SubjectPersonRelation, error)) *PersonService_GetSubjectRelated_Call { +func (_c *PersonService_GetSubjectRelated_Call) RunAndReturn(run func(ctx context.Context, subjectID model.SubjectID) ([]model.SubjectPersonRelation, error)) *PersonService_GetSubjectRelated_Call { _c.Call.Return(run) return _c } - -type mockConstructorTestingTNewPersonService interface { - mock.TestingT - Cleanup(func()) -} - -// NewPersonService creates a new instance of PersonService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewPersonService(t mockConstructorTestingTNewPersonService) *PersonService { - mock := &PersonService{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/mocks/PrivateMessageRepo.go b/internal/mocks/PrivateMessageRepo.go deleted file mode 100644 index 7a7639231..000000000 --- a/internal/mocks/PrivateMessageRepo.go +++ /dev/null @@ -1,461 +0,0 @@ -// Code generated by mockery v2.23.1. DO NOT EDIT. - -package mocks - -import ( - context "context" - - pm "github.com/bangumi/server/internal/pm" - mock "github.com/stretchr/testify/mock" -) - -// PrivateMessageRepo is an autogenerated mock type for the Repo type -type PrivateMessageRepo struct { - mock.Mock -} - -type PrivateMessageRepo_Expecter struct { - mock *mock.Mock -} - -func (_m *PrivateMessageRepo) EXPECT() *PrivateMessageRepo_Expecter { - return &PrivateMessageRepo_Expecter{mock: &_m.Mock} -} - -// CountByFolder provides a mock function with given fields: ctx, userID, folder -func (_m *PrivateMessageRepo) CountByFolder(ctx context.Context, userID uint32, folder pm.FolderType) (int64, error) { - ret := _m.Called(ctx, userID, folder) - - var r0 int64 - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, pm.FolderType) (int64, error)); ok { - return rf(ctx, userID, folder) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32, pm.FolderType) int64); ok { - r0 = rf(ctx, userID, folder) - } else { - r0 = ret.Get(0).(int64) - } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, pm.FolderType) error); ok { - r1 = rf(ctx, userID, folder) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// PrivateMessageRepo_CountByFolder_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CountByFolder' -type PrivateMessageRepo_CountByFolder_Call struct { - *mock.Call -} - -// CountByFolder is a helper method to define mock.On call -// - ctx context.Context -// - userID uint32 -// - folder pm.FolderType -func (_e *PrivateMessageRepo_Expecter) CountByFolder(ctx interface{}, userID interface{}, folder interface{}) *PrivateMessageRepo_CountByFolder_Call { - return &PrivateMessageRepo_CountByFolder_Call{Call: _e.mock.On("CountByFolder", ctx, userID, folder)} -} - -func (_c *PrivateMessageRepo_CountByFolder_Call) Run(run func(ctx context.Context, userID uint32, folder pm.FolderType)) *PrivateMessageRepo_CountByFolder_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(pm.FolderType)) - }) - return _c -} - -func (_c *PrivateMessageRepo_CountByFolder_Call) Return(_a0 int64, _a1 error) *PrivateMessageRepo_CountByFolder_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *PrivateMessageRepo_CountByFolder_Call) RunAndReturn(run func(context.Context, uint32, pm.FolderType) (int64, error)) *PrivateMessageRepo_CountByFolder_Call { - _c.Call.Return(run) - return _c -} - -// CountTypes provides a mock function with given fields: ctx, userID -func (_m *PrivateMessageRepo) CountTypes(ctx context.Context, userID uint32) (pm.PrivateMessageTypeCounts, error) { - ret := _m.Called(ctx, userID) - - var r0 pm.PrivateMessageTypeCounts - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (pm.PrivateMessageTypeCounts, error)); ok { - return rf(ctx, userID) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32) pm.PrivateMessageTypeCounts); ok { - r0 = rf(ctx, userID) - } else { - r0 = ret.Get(0).(pm.PrivateMessageTypeCounts) - } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, userID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// PrivateMessageRepo_CountTypes_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CountTypes' -type PrivateMessageRepo_CountTypes_Call struct { - *mock.Call -} - -// CountTypes is a helper method to define mock.On call -// - ctx context.Context -// - userID uint32 -func (_e *PrivateMessageRepo_Expecter) CountTypes(ctx interface{}, userID interface{}) *PrivateMessageRepo_CountTypes_Call { - return &PrivateMessageRepo_CountTypes_Call{Call: _e.mock.On("CountTypes", ctx, userID)} -} - -func (_c *PrivateMessageRepo_CountTypes_Call) Run(run func(ctx context.Context, userID uint32)) *PrivateMessageRepo_CountTypes_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) - }) - return _c -} - -func (_c *PrivateMessageRepo_CountTypes_Call) Return(_a0 pm.PrivateMessageTypeCounts, _a1 error) *PrivateMessageRepo_CountTypes_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *PrivateMessageRepo_CountTypes_Call) RunAndReturn(run func(context.Context, uint32) (pm.PrivateMessageTypeCounts, error)) *PrivateMessageRepo_CountTypes_Call { - _c.Call.Return(run) - return _c -} - -// Create provides a mock function with given fields: ctx, senderID, receiverIDs, relatedIDFilter, title, content -func (_m *PrivateMessageRepo) Create(ctx context.Context, senderID uint32, receiverIDs []uint32, relatedIDFilter pm.IDFilter, title string, content string) ([]pm.PrivateMessage, error) { - ret := _m.Called(ctx, senderID, receiverIDs, relatedIDFilter, title, content) - - var r0 []pm.PrivateMessage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, []uint32, pm.IDFilter, string, string) ([]pm.PrivateMessage, error)); ok { - return rf(ctx, senderID, receiverIDs, relatedIDFilter, title, content) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32, []uint32, pm.IDFilter, string, string) []pm.PrivateMessage); ok { - r0 = rf(ctx, senderID, receiverIDs, relatedIDFilter, title, content) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]pm.PrivateMessage) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, []uint32, pm.IDFilter, string, string) error); ok { - r1 = rf(ctx, senderID, receiverIDs, relatedIDFilter, title, content) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// PrivateMessageRepo_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' -type PrivateMessageRepo_Create_Call struct { - *mock.Call -} - -// Create is a helper method to define mock.On call -// - ctx context.Context -// - senderID uint32 -// - receiverIDs []uint32 -// - relatedIDFilter pm.IDFilter -// - title string -// - content string -func (_e *PrivateMessageRepo_Expecter) Create(ctx interface{}, senderID interface{}, receiverIDs interface{}, relatedIDFilter interface{}, title interface{}, content interface{}) *PrivateMessageRepo_Create_Call { - return &PrivateMessageRepo_Create_Call{Call: _e.mock.On("Create", ctx, senderID, receiverIDs, relatedIDFilter, title, content)} -} - -func (_c *PrivateMessageRepo_Create_Call) Run(run func(ctx context.Context, senderID uint32, receiverIDs []uint32, relatedIDFilter pm.IDFilter, title string, content string)) *PrivateMessageRepo_Create_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].([]uint32), args[3].(pm.IDFilter), args[4].(string), args[5].(string)) - }) - return _c -} - -func (_c *PrivateMessageRepo_Create_Call) Return(_a0 []pm.PrivateMessage, _a1 error) *PrivateMessageRepo_Create_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *PrivateMessageRepo_Create_Call) RunAndReturn(run func(context.Context, uint32, []uint32, pm.IDFilter, string, string) ([]pm.PrivateMessage, error)) *PrivateMessageRepo_Create_Call { - _c.Call.Return(run) - return _c -} - -// Delete provides a mock function with given fields: ctx, userID, ids -func (_m *PrivateMessageRepo) Delete(ctx context.Context, userID uint32, ids []uint32) error { - ret := _m.Called(ctx, userID, ids) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, []uint32) error); ok { - r0 = rf(ctx, userID, ids) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// PrivateMessageRepo_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' -type PrivateMessageRepo_Delete_Call struct { - *mock.Call -} - -// Delete is a helper method to define mock.On call -// - ctx context.Context -// - userID uint32 -// - ids []uint32 -func (_e *PrivateMessageRepo_Expecter) Delete(ctx interface{}, userID interface{}, ids interface{}) *PrivateMessageRepo_Delete_Call { - return &PrivateMessageRepo_Delete_Call{Call: _e.mock.On("Delete", ctx, userID, ids)} -} - -func (_c *PrivateMessageRepo_Delete_Call) Run(run func(ctx context.Context, userID uint32, ids []uint32)) *PrivateMessageRepo_Delete_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].([]uint32)) - }) - return _c -} - -func (_c *PrivateMessageRepo_Delete_Call) Return(_a0 error) *PrivateMessageRepo_Delete_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *PrivateMessageRepo_Delete_Call) RunAndReturn(run func(context.Context, uint32, []uint32) error) *PrivateMessageRepo_Delete_Call { - _c.Call.Return(run) - return _c -} - -// List provides a mock function with given fields: ctx, userID, folder, offset, limit -func (_m *PrivateMessageRepo) List(ctx context.Context, userID uint32, folder pm.FolderType, offset int, limit int) ([]pm.PrivateMessageListItem, error) { - ret := _m.Called(ctx, userID, folder, offset, limit) - - var r0 []pm.PrivateMessageListItem - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, pm.FolderType, int, int) ([]pm.PrivateMessageListItem, error)); ok { - return rf(ctx, userID, folder, offset, limit) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32, pm.FolderType, int, int) []pm.PrivateMessageListItem); ok { - r0 = rf(ctx, userID, folder, offset, limit) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]pm.PrivateMessageListItem) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, pm.FolderType, int, int) error); ok { - r1 = rf(ctx, userID, folder, offset, limit) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// PrivateMessageRepo_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List' -type PrivateMessageRepo_List_Call struct { - *mock.Call -} - -// List is a helper method to define mock.On call -// - ctx context.Context -// - userID uint32 -// - folder pm.FolderType -// - offset int -// - limit int -func (_e *PrivateMessageRepo_Expecter) List(ctx interface{}, userID interface{}, folder interface{}, offset interface{}, limit interface{}) *PrivateMessageRepo_List_Call { - return &PrivateMessageRepo_List_Call{Call: _e.mock.On("List", ctx, userID, folder, offset, limit)} -} - -func (_c *PrivateMessageRepo_List_Call) Run(run func(ctx context.Context, userID uint32, folder pm.FolderType, offset int, limit int)) *PrivateMessageRepo_List_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(pm.FolderType), args[3].(int), args[4].(int)) - }) - return _c -} - -func (_c *PrivateMessageRepo_List_Call) Return(_a0 []pm.PrivateMessageListItem, _a1 error) *PrivateMessageRepo_List_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *PrivateMessageRepo_List_Call) RunAndReturn(run func(context.Context, uint32, pm.FolderType, int, int) ([]pm.PrivateMessageListItem, error)) *PrivateMessageRepo_List_Call { - _c.Call.Return(run) - return _c -} - -// ListRecentContact provides a mock function with given fields: ctx, userID -func (_m *PrivateMessageRepo) ListRecentContact(ctx context.Context, userID uint32) ([]uint32, error) { - ret := _m.Called(ctx, userID) - - var r0 []uint32 - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) ([]uint32, error)); ok { - return rf(ctx, userID) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32) []uint32); ok { - r0 = rf(ctx, userID) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]uint32) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, userID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// PrivateMessageRepo_ListRecentContact_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListRecentContact' -type PrivateMessageRepo_ListRecentContact_Call struct { - *mock.Call -} - -// ListRecentContact is a helper method to define mock.On call -// - ctx context.Context -// - userID uint32 -func (_e *PrivateMessageRepo_Expecter) ListRecentContact(ctx interface{}, userID interface{}) *PrivateMessageRepo_ListRecentContact_Call { - return &PrivateMessageRepo_ListRecentContact_Call{Call: _e.mock.On("ListRecentContact", ctx, userID)} -} - -func (_c *PrivateMessageRepo_ListRecentContact_Call) Run(run func(ctx context.Context, userID uint32)) *PrivateMessageRepo_ListRecentContact_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) - }) - return _c -} - -func (_c *PrivateMessageRepo_ListRecentContact_Call) Return(_a0 []uint32, _a1 error) *PrivateMessageRepo_ListRecentContact_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *PrivateMessageRepo_ListRecentContact_Call) RunAndReturn(run func(context.Context, uint32) ([]uint32, error)) *PrivateMessageRepo_ListRecentContact_Call { - _c.Call.Return(run) - return _c -} - -// ListRelated provides a mock function with given fields: ctx, userID, id -func (_m *PrivateMessageRepo) ListRelated(ctx context.Context, userID uint32, id uint32) ([]pm.PrivateMessage, error) { - ret := _m.Called(ctx, userID, id) - - var r0 []pm.PrivateMessage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint32) ([]pm.PrivateMessage, error)); ok { - return rf(ctx, userID, id) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint32) []pm.PrivateMessage); ok { - r0 = rf(ctx, userID, id) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]pm.PrivateMessage) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, uint32) error); ok { - r1 = rf(ctx, userID, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// PrivateMessageRepo_ListRelated_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListRelated' -type PrivateMessageRepo_ListRelated_Call struct { - *mock.Call -} - -// ListRelated is a helper method to define mock.On call -// - ctx context.Context -// - userID uint32 -// - id uint32 -func (_e *PrivateMessageRepo_Expecter) ListRelated(ctx interface{}, userID interface{}, id interface{}) *PrivateMessageRepo_ListRelated_Call { - return &PrivateMessageRepo_ListRelated_Call{Call: _e.mock.On("ListRelated", ctx, userID, id)} -} - -func (_c *PrivateMessageRepo_ListRelated_Call) Run(run func(ctx context.Context, userID uint32, id uint32)) *PrivateMessageRepo_ListRelated_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(uint32)) - }) - return _c -} - -func (_c *PrivateMessageRepo_ListRelated_Call) Return(_a0 []pm.PrivateMessage, _a1 error) *PrivateMessageRepo_ListRelated_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *PrivateMessageRepo_ListRelated_Call) RunAndReturn(run func(context.Context, uint32, uint32) ([]pm.PrivateMessage, error)) *PrivateMessageRepo_ListRelated_Call { - _c.Call.Return(run) - return _c -} - -// MarkRead provides a mock function with given fields: ctx, userID, relatedID -func (_m *PrivateMessageRepo) MarkRead(ctx context.Context, userID uint32, relatedID uint32) error { - ret := _m.Called(ctx, userID, relatedID) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint32) error); ok { - r0 = rf(ctx, userID, relatedID) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// PrivateMessageRepo_MarkRead_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MarkRead' -type PrivateMessageRepo_MarkRead_Call struct { - *mock.Call -} - -// MarkRead is a helper method to define mock.On call -// - ctx context.Context -// - userID uint32 -// - relatedID uint32 -func (_e *PrivateMessageRepo_Expecter) MarkRead(ctx interface{}, userID interface{}, relatedID interface{}) *PrivateMessageRepo_MarkRead_Call { - return &PrivateMessageRepo_MarkRead_Call{Call: _e.mock.On("MarkRead", ctx, userID, relatedID)} -} - -func (_c *PrivateMessageRepo_MarkRead_Call) Run(run func(ctx context.Context, userID uint32, relatedID uint32)) *PrivateMessageRepo_MarkRead_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(uint32)) - }) - return _c -} - -func (_c *PrivateMessageRepo_MarkRead_Call) Return(_a0 error) *PrivateMessageRepo_MarkRead_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *PrivateMessageRepo_MarkRead_Call) RunAndReturn(run func(context.Context, uint32, uint32) error) *PrivateMessageRepo_MarkRead_Call { - _c.Call.Return(run) - return _c -} - -type mockConstructorTestingTNewPrivateMessageRepo interface { - mock.TestingT - Cleanup(func()) -} - -// NewPrivateMessageRepo creates a new instance of PrivateMessageRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewPrivateMessageRepo(t mockConstructorTestingTNewPrivateMessageRepo) *PrivateMessageRepo { - mock := &PrivateMessageRepo{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/mocks/RedisCache.go b/internal/mocks/RedisCache.go deleted file mode 100644 index caba679d3..000000000 --- a/internal/mocks/RedisCache.go +++ /dev/null @@ -1,194 +0,0 @@ -// Code generated by mockery v2.23.1. DO NOT EDIT. - -package mocks - -import ( - context "context" - time "time" - - mock "github.com/stretchr/testify/mock" -) - -// RedisCache is an autogenerated mock type for the RedisCache type -type RedisCache struct { - mock.Mock -} - -type RedisCache_Expecter struct { - mock *mock.Mock -} - -func (_m *RedisCache) EXPECT() *RedisCache_Expecter { - return &RedisCache_Expecter{mock: &_m.Mock} -} - -// Del provides a mock function with given fields: ctx, keys -func (_m *RedisCache) Del(ctx context.Context, keys ...string) error { - _va := make([]interface{}, len(keys)) - for _i := range keys { - _va[_i] = keys[_i] - } - var _ca []interface{} - _ca = append(_ca, ctx) - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, ...string) error); ok { - r0 = rf(ctx, keys...) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// RedisCache_Del_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Del' -type RedisCache_Del_Call struct { - *mock.Call -} - -// Del is a helper method to define mock.On call -// - ctx context.Context -// - keys ...string -func (_e *RedisCache_Expecter) Del(ctx interface{}, keys ...interface{}) *RedisCache_Del_Call { - return &RedisCache_Del_Call{Call: _e.mock.On("Del", - append([]interface{}{ctx}, keys...)...)} -} - -func (_c *RedisCache_Del_Call) Run(run func(ctx context.Context, keys ...string)) *RedisCache_Del_Call { - _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]string, len(args)-1) - for i, a := range args[1:] { - if a != nil { - variadicArgs[i] = a.(string) - } - } - run(args[0].(context.Context), variadicArgs...) - }) - return _c -} - -func (_c *RedisCache_Del_Call) Return(_a0 error) *RedisCache_Del_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *RedisCache_Del_Call) RunAndReturn(run func(context.Context, ...string) error) *RedisCache_Del_Call { - _c.Call.Return(run) - return _c -} - -// Get provides a mock function with given fields: ctx, key, value -func (_m *RedisCache) Get(ctx context.Context, key string, value interface{}) (bool, error) { - ret := _m.Called(ctx, key, value) - - var r0 bool - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, interface{}) (bool, error)); ok { - return rf(ctx, key, value) - } - if rf, ok := ret.Get(0).(func(context.Context, string, interface{}) bool); ok { - r0 = rf(ctx, key, value) - } else { - r0 = ret.Get(0).(bool) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, interface{}) error); ok { - r1 = rf(ctx, key, value) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// RedisCache_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' -type RedisCache_Get_Call struct { - *mock.Call -} - -// Get is a helper method to define mock.On call -// - ctx context.Context -// - key string -// - value interface{} -func (_e *RedisCache_Expecter) Get(ctx interface{}, key interface{}, value interface{}) *RedisCache_Get_Call { - return &RedisCache_Get_Call{Call: _e.mock.On("Get", ctx, key, value)} -} - -func (_c *RedisCache_Get_Call) Run(run func(ctx context.Context, key string, value interface{})) *RedisCache_Get_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(interface{})) - }) - return _c -} - -func (_c *RedisCache_Get_Call) Return(_a0 bool, _a1 error) *RedisCache_Get_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *RedisCache_Get_Call) RunAndReturn(run func(context.Context, string, interface{}) (bool, error)) *RedisCache_Get_Call { - _c.Call.Return(run) - return _c -} - -// Set provides a mock function with given fields: ctx, key, value, ttl -func (_m *RedisCache) Set(ctx context.Context, key string, value interface{}, ttl time.Duration) error { - ret := _m.Called(ctx, key, value, ttl) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, interface{}, time.Duration) error); ok { - r0 = rf(ctx, key, value, ttl) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// RedisCache_Set_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Set' -type RedisCache_Set_Call struct { - *mock.Call -} - -// Set is a helper method to define mock.On call -// - ctx context.Context -// - key string -// - value interface{} -// - ttl time.Duration -func (_e *RedisCache_Expecter) Set(ctx interface{}, key interface{}, value interface{}, ttl interface{}) *RedisCache_Set_Call { - return &RedisCache_Set_Call{Call: _e.mock.On("Set", ctx, key, value, ttl)} -} - -func (_c *RedisCache_Set_Call) Run(run func(ctx context.Context, key string, value interface{}, ttl time.Duration)) *RedisCache_Set_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(interface{}), args[3].(time.Duration)) - }) - return _c -} - -func (_c *RedisCache_Set_Call) Return(_a0 error) *RedisCache_Set_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *RedisCache_Set_Call) RunAndReturn(run func(context.Context, string, interface{}, time.Duration) error) *RedisCache_Set_Call { - _c.Call.Return(run) - return _c -} - -type mockConstructorTestingTNewRedisCache interface { - mock.TestingT - Cleanup(func()) -} - -// NewRedisCache creates a new instance of RedisCache. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewRedisCache(t mockConstructorTestingTNewRedisCache) *RedisCache { - mock := &RedisCache{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/mocks/RevisionRepo.go b/internal/mocks/RevisionRepo.go index 82abe5074..ee14e0993 100644 --- a/internal/mocks/RevisionRepo.go +++ b/internal/mocks/RevisionRepo.go @@ -1,14 +1,30 @@ -// Code generated by mockery v2.23.1. DO NOT EDIT. +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify package mocks import ( - context "context" + "context" - model "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/model" mock "github.com/stretchr/testify/mock" ) +// NewRevisionRepo creates a new instance of RevisionRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewRevisionRepo(t interface { + mock.TestingT + Cleanup(func()) +}) *RevisionRepo { + mock := &RevisionRepo{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + // RevisionRepo is an autogenerated mock type for the Repo type type RevisionRepo struct { mock.Mock @@ -22,27 +38,29 @@ func (_m *RevisionRepo) EXPECT() *RevisionRepo_Expecter { return &RevisionRepo_Expecter{mock: &_m.Mock} } -// CountCharacterRelated provides a mock function with given fields: ctx, characterID -func (_m *RevisionRepo) CountCharacterRelated(ctx context.Context, characterID uint32) (int64, error) { - ret := _m.Called(ctx, characterID) +// CountCharacterRelated provides a mock function for the type RevisionRepo +func (_mock *RevisionRepo) CountCharacterRelated(ctx context.Context, characterID model.CharacterID) (int64, error) { + ret := _mock.Called(ctx, characterID) + + if len(ret) == 0 { + panic("no return value specified for CountCharacterRelated") + } var r0 int64 var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (int64, error)); ok { - return rf(ctx, characterID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.CharacterID) (int64, error)); ok { + return returnFunc(ctx, characterID) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) int64); ok { - r0 = rf(ctx, characterID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.CharacterID) int64); ok { + r0 = returnFunc(ctx, characterID) } else { r0 = ret.Get(0).(int64) } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, characterID) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.CharacterID) error); ok { + r1 = returnFunc(ctx, characterID) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -53,49 +71,62 @@ type RevisionRepo_CountCharacterRelated_Call struct { // CountCharacterRelated is a helper method to define mock.On call // - ctx context.Context -// - characterID uint32 +// - characterID model.CharacterID func (_e *RevisionRepo_Expecter) CountCharacterRelated(ctx interface{}, characterID interface{}) *RevisionRepo_CountCharacterRelated_Call { return &RevisionRepo_CountCharacterRelated_Call{Call: _e.mock.On("CountCharacterRelated", ctx, characterID)} } -func (_c *RevisionRepo_CountCharacterRelated_Call) Run(run func(ctx context.Context, characterID uint32)) *RevisionRepo_CountCharacterRelated_Call { +func (_c *RevisionRepo_CountCharacterRelated_Call) Run(run func(ctx context.Context, characterID model.CharacterID)) *RevisionRepo_CountCharacterRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.CharacterID + if args[1] != nil { + arg1 = args[1].(model.CharacterID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *RevisionRepo_CountCharacterRelated_Call) Return(_a0 int64, _a1 error) *RevisionRepo_CountCharacterRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *RevisionRepo_CountCharacterRelated_Call) Return(n int64, err error) *RevisionRepo_CountCharacterRelated_Call { + _c.Call.Return(n, err) return _c } -func (_c *RevisionRepo_CountCharacterRelated_Call) RunAndReturn(run func(context.Context, uint32) (int64, error)) *RevisionRepo_CountCharacterRelated_Call { +func (_c *RevisionRepo_CountCharacterRelated_Call) RunAndReturn(run func(ctx context.Context, characterID model.CharacterID) (int64, error)) *RevisionRepo_CountCharacterRelated_Call { _c.Call.Return(run) return _c } -// CountEpisodeRelated provides a mock function with given fields: ctx, episodeID -func (_m *RevisionRepo) CountEpisodeRelated(ctx context.Context, episodeID uint32) (int64, error) { - ret := _m.Called(ctx, episodeID) +// CountEpisodeRelated provides a mock function for the type RevisionRepo +func (_mock *RevisionRepo) CountEpisodeRelated(ctx context.Context, episodeID model.EpisodeID) (int64, error) { + ret := _mock.Called(ctx, episodeID) + + if len(ret) == 0 { + panic("no return value specified for CountEpisodeRelated") + } var r0 int64 var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (int64, error)); ok { - return rf(ctx, episodeID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.EpisodeID) (int64, error)); ok { + return returnFunc(ctx, episodeID) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) int64); ok { - r0 = rf(ctx, episodeID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.EpisodeID) int64); ok { + r0 = returnFunc(ctx, episodeID) } else { r0 = ret.Get(0).(int64) } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, episodeID) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.EpisodeID) error); ok { + r1 = returnFunc(ctx, episodeID) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -106,49 +137,62 @@ type RevisionRepo_CountEpisodeRelated_Call struct { // CountEpisodeRelated is a helper method to define mock.On call // - ctx context.Context -// - episodeID uint32 +// - episodeID model.EpisodeID func (_e *RevisionRepo_Expecter) CountEpisodeRelated(ctx interface{}, episodeID interface{}) *RevisionRepo_CountEpisodeRelated_Call { return &RevisionRepo_CountEpisodeRelated_Call{Call: _e.mock.On("CountEpisodeRelated", ctx, episodeID)} } -func (_c *RevisionRepo_CountEpisodeRelated_Call) Run(run func(ctx context.Context, episodeID uint32)) *RevisionRepo_CountEpisodeRelated_Call { +func (_c *RevisionRepo_CountEpisodeRelated_Call) Run(run func(ctx context.Context, episodeID model.EpisodeID)) *RevisionRepo_CountEpisodeRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.EpisodeID + if args[1] != nil { + arg1 = args[1].(model.EpisodeID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *RevisionRepo_CountEpisodeRelated_Call) Return(_a0 int64, _a1 error) *RevisionRepo_CountEpisodeRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *RevisionRepo_CountEpisodeRelated_Call) Return(n int64, err error) *RevisionRepo_CountEpisodeRelated_Call { + _c.Call.Return(n, err) return _c } -func (_c *RevisionRepo_CountEpisodeRelated_Call) RunAndReturn(run func(context.Context, uint32) (int64, error)) *RevisionRepo_CountEpisodeRelated_Call { +func (_c *RevisionRepo_CountEpisodeRelated_Call) RunAndReturn(run func(ctx context.Context, episodeID model.EpisodeID) (int64, error)) *RevisionRepo_CountEpisodeRelated_Call { _c.Call.Return(run) return _c } -// CountPersonRelated provides a mock function with given fields: ctx, personID -func (_m *RevisionRepo) CountPersonRelated(ctx context.Context, personID uint32) (int64, error) { - ret := _m.Called(ctx, personID) +// CountPersonRelated provides a mock function for the type RevisionRepo +func (_mock *RevisionRepo) CountPersonRelated(ctx context.Context, personID model.PersonID) (int64, error) { + ret := _mock.Called(ctx, personID) + + if len(ret) == 0 { + panic("no return value specified for CountPersonRelated") + } var r0 int64 var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (int64, error)); ok { - return rf(ctx, personID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.PersonID) (int64, error)); ok { + return returnFunc(ctx, personID) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) int64); ok { - r0 = rf(ctx, personID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.PersonID) int64); ok { + r0 = returnFunc(ctx, personID) } else { r0 = ret.Get(0).(int64) } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, personID) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.PersonID) error); ok { + r1 = returnFunc(ctx, personID) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -159,49 +203,62 @@ type RevisionRepo_CountPersonRelated_Call struct { // CountPersonRelated is a helper method to define mock.On call // - ctx context.Context -// - personID uint32 +// - personID model.PersonID func (_e *RevisionRepo_Expecter) CountPersonRelated(ctx interface{}, personID interface{}) *RevisionRepo_CountPersonRelated_Call { return &RevisionRepo_CountPersonRelated_Call{Call: _e.mock.On("CountPersonRelated", ctx, personID)} } -func (_c *RevisionRepo_CountPersonRelated_Call) Run(run func(ctx context.Context, personID uint32)) *RevisionRepo_CountPersonRelated_Call { +func (_c *RevisionRepo_CountPersonRelated_Call) Run(run func(ctx context.Context, personID model.PersonID)) *RevisionRepo_CountPersonRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.PersonID + if args[1] != nil { + arg1 = args[1].(model.PersonID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *RevisionRepo_CountPersonRelated_Call) Return(_a0 int64, _a1 error) *RevisionRepo_CountPersonRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *RevisionRepo_CountPersonRelated_Call) Return(n int64, err error) *RevisionRepo_CountPersonRelated_Call { + _c.Call.Return(n, err) return _c } -func (_c *RevisionRepo_CountPersonRelated_Call) RunAndReturn(run func(context.Context, uint32) (int64, error)) *RevisionRepo_CountPersonRelated_Call { +func (_c *RevisionRepo_CountPersonRelated_Call) RunAndReturn(run func(ctx context.Context, personID model.PersonID) (int64, error)) *RevisionRepo_CountPersonRelated_Call { _c.Call.Return(run) return _c } -// CountSubjectRelated provides a mock function with given fields: ctx, id -func (_m *RevisionRepo) CountSubjectRelated(ctx context.Context, id uint32) (int64, error) { - ret := _m.Called(ctx, id) +// CountSubjectRelated provides a mock function for the type RevisionRepo +func (_mock *RevisionRepo) CountSubjectRelated(ctx context.Context, id model.SubjectID) (int64, error) { + ret := _mock.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for CountSubjectRelated") + } var r0 int64 var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (int64, error)); ok { - return rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID) (int64, error)); ok { + return returnFunc(ctx, id) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) int64); ok { - r0 = rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID) int64); ok { + r0 = returnFunc(ctx, id) } else { r0 = ret.Get(0).(int64) } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, id) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.SubjectID) error); ok { + r1 = returnFunc(ctx, id) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -212,49 +269,62 @@ type RevisionRepo_CountSubjectRelated_Call struct { // CountSubjectRelated is a helper method to define mock.On call // - ctx context.Context -// - id uint32 +// - id model.SubjectID func (_e *RevisionRepo_Expecter) CountSubjectRelated(ctx interface{}, id interface{}) *RevisionRepo_CountSubjectRelated_Call { return &RevisionRepo_CountSubjectRelated_Call{Call: _e.mock.On("CountSubjectRelated", ctx, id)} } -func (_c *RevisionRepo_CountSubjectRelated_Call) Run(run func(ctx context.Context, id uint32)) *RevisionRepo_CountSubjectRelated_Call { +func (_c *RevisionRepo_CountSubjectRelated_Call) Run(run func(ctx context.Context, id model.SubjectID)) *RevisionRepo_CountSubjectRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.SubjectID + if args[1] != nil { + arg1 = args[1].(model.SubjectID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *RevisionRepo_CountSubjectRelated_Call) Return(_a0 int64, _a1 error) *RevisionRepo_CountSubjectRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *RevisionRepo_CountSubjectRelated_Call) Return(n int64, err error) *RevisionRepo_CountSubjectRelated_Call { + _c.Call.Return(n, err) return _c } -func (_c *RevisionRepo_CountSubjectRelated_Call) RunAndReturn(run func(context.Context, uint32) (int64, error)) *RevisionRepo_CountSubjectRelated_Call { +func (_c *RevisionRepo_CountSubjectRelated_Call) RunAndReturn(run func(ctx context.Context, id model.SubjectID) (int64, error)) *RevisionRepo_CountSubjectRelated_Call { _c.Call.Return(run) return _c } -// GetCharacterRelated provides a mock function with given fields: ctx, id -func (_m *RevisionRepo) GetCharacterRelated(ctx context.Context, id uint32) (model.CharacterRevision, error) { - ret := _m.Called(ctx, id) +// GetCharacterRelated provides a mock function for the type RevisionRepo +func (_mock *RevisionRepo) GetCharacterRelated(ctx context.Context, id model.RevisionID) (model.CharacterRevision, error) { + ret := _mock.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for GetCharacterRelated") + } var r0 model.CharacterRevision var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (model.CharacterRevision, error)); ok { - return rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.RevisionID) (model.CharacterRevision, error)); ok { + return returnFunc(ctx, id) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) model.CharacterRevision); ok { - r0 = rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.RevisionID) model.CharacterRevision); ok { + r0 = returnFunc(ctx, id) } else { r0 = ret.Get(0).(model.CharacterRevision) } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, id) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.RevisionID) error); ok { + r1 = returnFunc(ctx, id) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -265,49 +335,62 @@ type RevisionRepo_GetCharacterRelated_Call struct { // GetCharacterRelated is a helper method to define mock.On call // - ctx context.Context -// - id uint32 +// - id model.RevisionID func (_e *RevisionRepo_Expecter) GetCharacterRelated(ctx interface{}, id interface{}) *RevisionRepo_GetCharacterRelated_Call { return &RevisionRepo_GetCharacterRelated_Call{Call: _e.mock.On("GetCharacterRelated", ctx, id)} } -func (_c *RevisionRepo_GetCharacterRelated_Call) Run(run func(ctx context.Context, id uint32)) *RevisionRepo_GetCharacterRelated_Call { +func (_c *RevisionRepo_GetCharacterRelated_Call) Run(run func(ctx context.Context, id model.RevisionID)) *RevisionRepo_GetCharacterRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.RevisionID + if args[1] != nil { + arg1 = args[1].(model.RevisionID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *RevisionRepo_GetCharacterRelated_Call) Return(_a0 model.CharacterRevision, _a1 error) *RevisionRepo_GetCharacterRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *RevisionRepo_GetCharacterRelated_Call) Return(characterRevision model.CharacterRevision, err error) *RevisionRepo_GetCharacterRelated_Call { + _c.Call.Return(characterRevision, err) return _c } -func (_c *RevisionRepo_GetCharacterRelated_Call) RunAndReturn(run func(context.Context, uint32) (model.CharacterRevision, error)) *RevisionRepo_GetCharacterRelated_Call { +func (_c *RevisionRepo_GetCharacterRelated_Call) RunAndReturn(run func(ctx context.Context, id model.RevisionID) (model.CharacterRevision, error)) *RevisionRepo_GetCharacterRelated_Call { _c.Call.Return(run) return _c } -// GetEpisodeRelated provides a mock function with given fields: ctx, id -func (_m *RevisionRepo) GetEpisodeRelated(ctx context.Context, id uint32) (model.EpisodeRevision, error) { - ret := _m.Called(ctx, id) +// GetEpisodeRelated provides a mock function for the type RevisionRepo +func (_mock *RevisionRepo) GetEpisodeRelated(ctx context.Context, id model.RevisionID) (model.EpisodeRevision, error) { + ret := _mock.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for GetEpisodeRelated") + } var r0 model.EpisodeRevision var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (model.EpisodeRevision, error)); ok { - return rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.RevisionID) (model.EpisodeRevision, error)); ok { + return returnFunc(ctx, id) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) model.EpisodeRevision); ok { - r0 = rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.RevisionID) model.EpisodeRevision); ok { + r0 = returnFunc(ctx, id) } else { r0 = ret.Get(0).(model.EpisodeRevision) } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, id) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.RevisionID) error); ok { + r1 = returnFunc(ctx, id) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -318,49 +401,62 @@ type RevisionRepo_GetEpisodeRelated_Call struct { // GetEpisodeRelated is a helper method to define mock.On call // - ctx context.Context -// - id uint32 +// - id model.RevisionID func (_e *RevisionRepo_Expecter) GetEpisodeRelated(ctx interface{}, id interface{}) *RevisionRepo_GetEpisodeRelated_Call { return &RevisionRepo_GetEpisodeRelated_Call{Call: _e.mock.On("GetEpisodeRelated", ctx, id)} } -func (_c *RevisionRepo_GetEpisodeRelated_Call) Run(run func(ctx context.Context, id uint32)) *RevisionRepo_GetEpisodeRelated_Call { +func (_c *RevisionRepo_GetEpisodeRelated_Call) Run(run func(ctx context.Context, id model.RevisionID)) *RevisionRepo_GetEpisodeRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.RevisionID + if args[1] != nil { + arg1 = args[1].(model.RevisionID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *RevisionRepo_GetEpisodeRelated_Call) Return(_a0 model.EpisodeRevision, _a1 error) *RevisionRepo_GetEpisodeRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *RevisionRepo_GetEpisodeRelated_Call) Return(episodeRevision model.EpisodeRevision, err error) *RevisionRepo_GetEpisodeRelated_Call { + _c.Call.Return(episodeRevision, err) return _c } -func (_c *RevisionRepo_GetEpisodeRelated_Call) RunAndReturn(run func(context.Context, uint32) (model.EpisodeRevision, error)) *RevisionRepo_GetEpisodeRelated_Call { +func (_c *RevisionRepo_GetEpisodeRelated_Call) RunAndReturn(run func(ctx context.Context, id model.RevisionID) (model.EpisodeRevision, error)) *RevisionRepo_GetEpisodeRelated_Call { _c.Call.Return(run) return _c } -// GetPersonRelated provides a mock function with given fields: ctx, id -func (_m *RevisionRepo) GetPersonRelated(ctx context.Context, id uint32) (model.PersonRevision, error) { - ret := _m.Called(ctx, id) +// GetPersonRelated provides a mock function for the type RevisionRepo +func (_mock *RevisionRepo) GetPersonRelated(ctx context.Context, id model.RevisionID) (model.PersonRevision, error) { + ret := _mock.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for GetPersonRelated") + } var r0 model.PersonRevision var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (model.PersonRevision, error)); ok { - return rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.RevisionID) (model.PersonRevision, error)); ok { + return returnFunc(ctx, id) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) model.PersonRevision); ok { - r0 = rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.RevisionID) model.PersonRevision); ok { + r0 = returnFunc(ctx, id) } else { r0 = ret.Get(0).(model.PersonRevision) } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, id) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.RevisionID) error); ok { + r1 = returnFunc(ctx, id) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -371,49 +467,62 @@ type RevisionRepo_GetPersonRelated_Call struct { // GetPersonRelated is a helper method to define mock.On call // - ctx context.Context -// - id uint32 +// - id model.RevisionID func (_e *RevisionRepo_Expecter) GetPersonRelated(ctx interface{}, id interface{}) *RevisionRepo_GetPersonRelated_Call { return &RevisionRepo_GetPersonRelated_Call{Call: _e.mock.On("GetPersonRelated", ctx, id)} } -func (_c *RevisionRepo_GetPersonRelated_Call) Run(run func(ctx context.Context, id uint32)) *RevisionRepo_GetPersonRelated_Call { +func (_c *RevisionRepo_GetPersonRelated_Call) Run(run func(ctx context.Context, id model.RevisionID)) *RevisionRepo_GetPersonRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.RevisionID + if args[1] != nil { + arg1 = args[1].(model.RevisionID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *RevisionRepo_GetPersonRelated_Call) Return(_a0 model.PersonRevision, _a1 error) *RevisionRepo_GetPersonRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *RevisionRepo_GetPersonRelated_Call) Return(personRevision model.PersonRevision, err error) *RevisionRepo_GetPersonRelated_Call { + _c.Call.Return(personRevision, err) return _c } -func (_c *RevisionRepo_GetPersonRelated_Call) RunAndReturn(run func(context.Context, uint32) (model.PersonRevision, error)) *RevisionRepo_GetPersonRelated_Call { +func (_c *RevisionRepo_GetPersonRelated_Call) RunAndReturn(run func(ctx context.Context, id model.RevisionID) (model.PersonRevision, error)) *RevisionRepo_GetPersonRelated_Call { _c.Call.Return(run) return _c } -// GetSubjectRelated provides a mock function with given fields: ctx, id -func (_m *RevisionRepo) GetSubjectRelated(ctx context.Context, id uint32) (model.SubjectRevision, error) { - ret := _m.Called(ctx, id) +// GetSubjectRelated provides a mock function for the type RevisionRepo +func (_mock *RevisionRepo) GetSubjectRelated(ctx context.Context, id model.RevisionID) (model.SubjectRevision, error) { + ret := _mock.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for GetSubjectRelated") + } var r0 model.SubjectRevision var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (model.SubjectRevision, error)); ok { - return rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.RevisionID) (model.SubjectRevision, error)); ok { + return returnFunc(ctx, id) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) model.SubjectRevision); ok { - r0 = rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.RevisionID) model.SubjectRevision); ok { + r0 = returnFunc(ctx, id) } else { r0 = ret.Get(0).(model.SubjectRevision) } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, id) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.RevisionID) error); ok { + r1 = returnFunc(ctx, id) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -424,51 +533,64 @@ type RevisionRepo_GetSubjectRelated_Call struct { // GetSubjectRelated is a helper method to define mock.On call // - ctx context.Context -// - id uint32 +// - id model.RevisionID func (_e *RevisionRepo_Expecter) GetSubjectRelated(ctx interface{}, id interface{}) *RevisionRepo_GetSubjectRelated_Call { return &RevisionRepo_GetSubjectRelated_Call{Call: _e.mock.On("GetSubjectRelated", ctx, id)} } -func (_c *RevisionRepo_GetSubjectRelated_Call) Run(run func(ctx context.Context, id uint32)) *RevisionRepo_GetSubjectRelated_Call { +func (_c *RevisionRepo_GetSubjectRelated_Call) Run(run func(ctx context.Context, id model.RevisionID)) *RevisionRepo_GetSubjectRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.RevisionID + if args[1] != nil { + arg1 = args[1].(model.RevisionID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *RevisionRepo_GetSubjectRelated_Call) Return(_a0 model.SubjectRevision, _a1 error) *RevisionRepo_GetSubjectRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *RevisionRepo_GetSubjectRelated_Call) Return(subjectRevision model.SubjectRevision, err error) *RevisionRepo_GetSubjectRelated_Call { + _c.Call.Return(subjectRevision, err) return _c } -func (_c *RevisionRepo_GetSubjectRelated_Call) RunAndReturn(run func(context.Context, uint32) (model.SubjectRevision, error)) *RevisionRepo_GetSubjectRelated_Call { +func (_c *RevisionRepo_GetSubjectRelated_Call) RunAndReturn(run func(ctx context.Context, id model.RevisionID) (model.SubjectRevision, error)) *RevisionRepo_GetSubjectRelated_Call { _c.Call.Return(run) return _c } -// ListCharacterRelated provides a mock function with given fields: ctx, characterID, limit, offset -func (_m *RevisionRepo) ListCharacterRelated(ctx context.Context, characterID uint32, limit int, offset int) ([]model.CharacterRevision, error) { - ret := _m.Called(ctx, characterID, limit, offset) +// ListCharacterRelated provides a mock function for the type RevisionRepo +func (_mock *RevisionRepo) ListCharacterRelated(ctx context.Context, characterID model.CharacterID, limit int, offset int) ([]model.CharacterRevision, error) { + ret := _mock.Called(ctx, characterID, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for ListCharacterRelated") + } var r0 []model.CharacterRevision var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, int, int) ([]model.CharacterRevision, error)); ok { - return rf(ctx, characterID, limit, offset) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.CharacterID, int, int) ([]model.CharacterRevision, error)); ok { + return returnFunc(ctx, characterID, limit, offset) } - if rf, ok := ret.Get(0).(func(context.Context, uint32, int, int) []model.CharacterRevision); ok { - r0 = rf(ctx, characterID, limit, offset) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.CharacterID, int, int) []model.CharacterRevision); ok { + r0 = returnFunc(ctx, characterID, limit, offset) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]model.CharacterRevision) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, int, int) error); ok { - r1 = rf(ctx, characterID, limit, offset) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.CharacterID, int, int) error); ok { + r1 = returnFunc(ctx, characterID, limit, offset) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -479,53 +601,76 @@ type RevisionRepo_ListCharacterRelated_Call struct { // ListCharacterRelated is a helper method to define mock.On call // - ctx context.Context -// - characterID uint32 +// - characterID model.CharacterID // - limit int // - offset int func (_e *RevisionRepo_Expecter) ListCharacterRelated(ctx interface{}, characterID interface{}, limit interface{}, offset interface{}) *RevisionRepo_ListCharacterRelated_Call { return &RevisionRepo_ListCharacterRelated_Call{Call: _e.mock.On("ListCharacterRelated", ctx, characterID, limit, offset)} } -func (_c *RevisionRepo_ListCharacterRelated_Call) Run(run func(ctx context.Context, characterID uint32, limit int, offset int)) *RevisionRepo_ListCharacterRelated_Call { +func (_c *RevisionRepo_ListCharacterRelated_Call) Run(run func(ctx context.Context, characterID model.CharacterID, limit int, offset int)) *RevisionRepo_ListCharacterRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(int), args[3].(int)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.CharacterID + if args[1] != nil { + arg1 = args[1].(model.CharacterID) + } + var arg2 int + if args[2] != nil { + arg2 = args[2].(int) + } + var arg3 int + if args[3] != nil { + arg3 = args[3].(int) + } + run( + arg0, + arg1, + arg2, + arg3, + ) }) return _c } -func (_c *RevisionRepo_ListCharacterRelated_Call) Return(_a0 []model.CharacterRevision, _a1 error) *RevisionRepo_ListCharacterRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *RevisionRepo_ListCharacterRelated_Call) Return(characterRevisions []model.CharacterRevision, err error) *RevisionRepo_ListCharacterRelated_Call { + _c.Call.Return(characterRevisions, err) return _c } -func (_c *RevisionRepo_ListCharacterRelated_Call) RunAndReturn(run func(context.Context, uint32, int, int) ([]model.CharacterRevision, error)) *RevisionRepo_ListCharacterRelated_Call { +func (_c *RevisionRepo_ListCharacterRelated_Call) RunAndReturn(run func(ctx context.Context, characterID model.CharacterID, limit int, offset int) ([]model.CharacterRevision, error)) *RevisionRepo_ListCharacterRelated_Call { _c.Call.Return(run) return _c } -// ListEpisodeRelated provides a mock function with given fields: ctx, episodeID, limit, offset -func (_m *RevisionRepo) ListEpisodeRelated(ctx context.Context, episodeID uint32, limit int, offset int) ([]model.EpisodeRevision, error) { - ret := _m.Called(ctx, episodeID, limit, offset) +// ListEpisodeRelated provides a mock function for the type RevisionRepo +func (_mock *RevisionRepo) ListEpisodeRelated(ctx context.Context, episodeID model.EpisodeID, limit int, offset int) ([]model.EpisodeRevision, error) { + ret := _mock.Called(ctx, episodeID, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for ListEpisodeRelated") + } var r0 []model.EpisodeRevision var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, int, int) ([]model.EpisodeRevision, error)); ok { - return rf(ctx, episodeID, limit, offset) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.EpisodeID, int, int) ([]model.EpisodeRevision, error)); ok { + return returnFunc(ctx, episodeID, limit, offset) } - if rf, ok := ret.Get(0).(func(context.Context, uint32, int, int) []model.EpisodeRevision); ok { - r0 = rf(ctx, episodeID, limit, offset) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.EpisodeID, int, int) []model.EpisodeRevision); ok { + r0 = returnFunc(ctx, episodeID, limit, offset) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]model.EpisodeRevision) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, int, int) error); ok { - r1 = rf(ctx, episodeID, limit, offset) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.EpisodeID, int, int) error); ok { + r1 = returnFunc(ctx, episodeID, limit, offset) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -536,53 +681,76 @@ type RevisionRepo_ListEpisodeRelated_Call struct { // ListEpisodeRelated is a helper method to define mock.On call // - ctx context.Context -// - episodeID uint32 +// - episodeID model.EpisodeID // - limit int // - offset int func (_e *RevisionRepo_Expecter) ListEpisodeRelated(ctx interface{}, episodeID interface{}, limit interface{}, offset interface{}) *RevisionRepo_ListEpisodeRelated_Call { return &RevisionRepo_ListEpisodeRelated_Call{Call: _e.mock.On("ListEpisodeRelated", ctx, episodeID, limit, offset)} } -func (_c *RevisionRepo_ListEpisodeRelated_Call) Run(run func(ctx context.Context, episodeID uint32, limit int, offset int)) *RevisionRepo_ListEpisodeRelated_Call { +func (_c *RevisionRepo_ListEpisodeRelated_Call) Run(run func(ctx context.Context, episodeID model.EpisodeID, limit int, offset int)) *RevisionRepo_ListEpisodeRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(int), args[3].(int)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.EpisodeID + if args[1] != nil { + arg1 = args[1].(model.EpisodeID) + } + var arg2 int + if args[2] != nil { + arg2 = args[2].(int) + } + var arg3 int + if args[3] != nil { + arg3 = args[3].(int) + } + run( + arg0, + arg1, + arg2, + arg3, + ) }) return _c } -func (_c *RevisionRepo_ListEpisodeRelated_Call) Return(_a0 []model.EpisodeRevision, _a1 error) *RevisionRepo_ListEpisodeRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *RevisionRepo_ListEpisodeRelated_Call) Return(episodeRevisions []model.EpisodeRevision, err error) *RevisionRepo_ListEpisodeRelated_Call { + _c.Call.Return(episodeRevisions, err) return _c } -func (_c *RevisionRepo_ListEpisodeRelated_Call) RunAndReturn(run func(context.Context, uint32, int, int) ([]model.EpisodeRevision, error)) *RevisionRepo_ListEpisodeRelated_Call { +func (_c *RevisionRepo_ListEpisodeRelated_Call) RunAndReturn(run func(ctx context.Context, episodeID model.EpisodeID, limit int, offset int) ([]model.EpisodeRevision, error)) *RevisionRepo_ListEpisodeRelated_Call { _c.Call.Return(run) return _c } -// ListPersonRelated provides a mock function with given fields: ctx, personID, limit, offset -func (_m *RevisionRepo) ListPersonRelated(ctx context.Context, personID uint32, limit int, offset int) ([]model.PersonRevision, error) { - ret := _m.Called(ctx, personID, limit, offset) +// ListPersonRelated provides a mock function for the type RevisionRepo +func (_mock *RevisionRepo) ListPersonRelated(ctx context.Context, personID model.PersonID, limit int, offset int) ([]model.PersonRevision, error) { + ret := _mock.Called(ctx, personID, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for ListPersonRelated") + } var r0 []model.PersonRevision var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, int, int) ([]model.PersonRevision, error)); ok { - return rf(ctx, personID, limit, offset) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.PersonID, int, int) ([]model.PersonRevision, error)); ok { + return returnFunc(ctx, personID, limit, offset) } - if rf, ok := ret.Get(0).(func(context.Context, uint32, int, int) []model.PersonRevision); ok { - r0 = rf(ctx, personID, limit, offset) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.PersonID, int, int) []model.PersonRevision); ok { + r0 = returnFunc(ctx, personID, limit, offset) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]model.PersonRevision) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, int, int) error); ok { - r1 = rf(ctx, personID, limit, offset) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.PersonID, int, int) error); ok { + r1 = returnFunc(ctx, personID, limit, offset) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -593,53 +761,76 @@ type RevisionRepo_ListPersonRelated_Call struct { // ListPersonRelated is a helper method to define mock.On call // - ctx context.Context -// - personID uint32 +// - personID model.PersonID // - limit int // - offset int func (_e *RevisionRepo_Expecter) ListPersonRelated(ctx interface{}, personID interface{}, limit interface{}, offset interface{}) *RevisionRepo_ListPersonRelated_Call { return &RevisionRepo_ListPersonRelated_Call{Call: _e.mock.On("ListPersonRelated", ctx, personID, limit, offset)} } -func (_c *RevisionRepo_ListPersonRelated_Call) Run(run func(ctx context.Context, personID uint32, limit int, offset int)) *RevisionRepo_ListPersonRelated_Call { +func (_c *RevisionRepo_ListPersonRelated_Call) Run(run func(ctx context.Context, personID model.PersonID, limit int, offset int)) *RevisionRepo_ListPersonRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(int), args[3].(int)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.PersonID + if args[1] != nil { + arg1 = args[1].(model.PersonID) + } + var arg2 int + if args[2] != nil { + arg2 = args[2].(int) + } + var arg3 int + if args[3] != nil { + arg3 = args[3].(int) + } + run( + arg0, + arg1, + arg2, + arg3, + ) }) return _c } -func (_c *RevisionRepo_ListPersonRelated_Call) Return(_a0 []model.PersonRevision, _a1 error) *RevisionRepo_ListPersonRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *RevisionRepo_ListPersonRelated_Call) Return(personRevisions []model.PersonRevision, err error) *RevisionRepo_ListPersonRelated_Call { + _c.Call.Return(personRevisions, err) return _c } -func (_c *RevisionRepo_ListPersonRelated_Call) RunAndReturn(run func(context.Context, uint32, int, int) ([]model.PersonRevision, error)) *RevisionRepo_ListPersonRelated_Call { +func (_c *RevisionRepo_ListPersonRelated_Call) RunAndReturn(run func(ctx context.Context, personID model.PersonID, limit int, offset int) ([]model.PersonRevision, error)) *RevisionRepo_ListPersonRelated_Call { _c.Call.Return(run) return _c } -// ListSubjectRelated provides a mock function with given fields: ctx, id, limit, offset -func (_m *RevisionRepo) ListSubjectRelated(ctx context.Context, id uint32, limit int, offset int) ([]model.SubjectRevision, error) { - ret := _m.Called(ctx, id, limit, offset) +// ListSubjectRelated provides a mock function for the type RevisionRepo +func (_mock *RevisionRepo) ListSubjectRelated(ctx context.Context, id model.SubjectID, limit int, offset int) ([]model.SubjectRevision, error) { + ret := _mock.Called(ctx, id, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for ListSubjectRelated") + } var r0 []model.SubjectRevision var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, int, int) ([]model.SubjectRevision, error)); ok { - return rf(ctx, id, limit, offset) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID, int, int) ([]model.SubjectRevision, error)); ok { + return returnFunc(ctx, id, limit, offset) } - if rf, ok := ret.Get(0).(func(context.Context, uint32, int, int) []model.SubjectRevision); ok { - r0 = rf(ctx, id, limit, offset) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID, int, int) []model.SubjectRevision); ok { + r0 = returnFunc(ctx, id, limit, offset) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]model.SubjectRevision) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, int, int) error); ok { - r1 = rf(ctx, id, limit, offset) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.SubjectID, int, int) error); ok { + r1 = returnFunc(ctx, id, limit, offset) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -650,41 +841,47 @@ type RevisionRepo_ListSubjectRelated_Call struct { // ListSubjectRelated is a helper method to define mock.On call // - ctx context.Context -// - id uint32 +// - id model.SubjectID // - limit int // - offset int func (_e *RevisionRepo_Expecter) ListSubjectRelated(ctx interface{}, id interface{}, limit interface{}, offset interface{}) *RevisionRepo_ListSubjectRelated_Call { return &RevisionRepo_ListSubjectRelated_Call{Call: _e.mock.On("ListSubjectRelated", ctx, id, limit, offset)} } -func (_c *RevisionRepo_ListSubjectRelated_Call) Run(run func(ctx context.Context, id uint32, limit int, offset int)) *RevisionRepo_ListSubjectRelated_Call { +func (_c *RevisionRepo_ListSubjectRelated_Call) Run(run func(ctx context.Context, id model.SubjectID, limit int, offset int)) *RevisionRepo_ListSubjectRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(int), args[3].(int)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.SubjectID + if args[1] != nil { + arg1 = args[1].(model.SubjectID) + } + var arg2 int + if args[2] != nil { + arg2 = args[2].(int) + } + var arg3 int + if args[3] != nil { + arg3 = args[3].(int) + } + run( + arg0, + arg1, + arg2, + arg3, + ) }) return _c } -func (_c *RevisionRepo_ListSubjectRelated_Call) Return(_a0 []model.SubjectRevision, _a1 error) *RevisionRepo_ListSubjectRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *RevisionRepo_ListSubjectRelated_Call) Return(subjectRevisions []model.SubjectRevision, err error) *RevisionRepo_ListSubjectRelated_Call { + _c.Call.Return(subjectRevisions, err) return _c } -func (_c *RevisionRepo_ListSubjectRelated_Call) RunAndReturn(run func(context.Context, uint32, int, int) ([]model.SubjectRevision, error)) *RevisionRepo_ListSubjectRelated_Call { +func (_c *RevisionRepo_ListSubjectRelated_Call) RunAndReturn(run func(ctx context.Context, id model.SubjectID, limit int, offset int) ([]model.SubjectRevision, error)) *RevisionRepo_ListSubjectRelated_Call { _c.Call.Return(run) return _c } - -type mockConstructorTestingTNewRevisionRepo interface { - mock.TestingT - Cleanup(func()) -} - -// NewRevisionRepo creates a new instance of RevisionRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewRevisionRepo(t mockConstructorTestingTNewRevisionRepo) *RevisionRepo { - mock := &RevisionRepo{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/mocks/SearchClient.go b/internal/mocks/SearchClient.go index bc7466ddc..0c13a1883 100644 --- a/internal/mocks/SearchClient.go +++ b/internal/mocks/SearchClient.go @@ -1,14 +1,31 @@ -// Code generated by mockery v2.23.1. DO NOT EDIT. +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify package mocks import ( - context "context" + "context" - echo "github.com/labstack/echo/v4" + "github.com/bangumi/server/internal/search" + "github.com/labstack/echo/v5" mock "github.com/stretchr/testify/mock" ) +// NewSearchClient creates a new instance of SearchClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewSearchClient(t interface { + mock.TestingT + Cleanup(func()) +}) *SearchClient { + mock := &SearchClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + // SearchClient is an autogenerated mock type for the Client type type SearchClient struct { mock.Mock @@ -22,9 +39,10 @@ func (_m *SearchClient) EXPECT() *SearchClient_Expecter { return &SearchClient_Expecter{mock: &_m.Mock} } -// Close provides a mock function with given fields: -func (_m *SearchClient) Close() { - _m.Called() +// Close provides a mock function for the type SearchClient +func (_mock *SearchClient) Close() { + _mock.Called() + return } // SearchClient_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' @@ -50,149 +68,252 @@ func (_c *SearchClient_Close_Call) Return() *SearchClient_Close_Call { } func (_c *SearchClient_Close_Call) RunAndReturn(run func()) *SearchClient_Close_Call { - _c.Call.Return(run) + _c.Run(run) return _c } -// Handle provides a mock function with given fields: c -func (_m *SearchClient) Handle(c echo.Context) error { - ret := _m.Called(c) +// EventAdded provides a mock function for the type SearchClient +func (_mock *SearchClient) EventAdded(ctx context.Context, id uint32, target search.SearchTarget) error { + ret := _mock.Called(ctx, id, target) + + if len(ret) == 0 { + panic("no return value specified for EventAdded") + } var r0 error - if rf, ok := ret.Get(0).(func(echo.Context) error); ok { - r0 = rf(c) + if returnFunc, ok := ret.Get(0).(func(context.Context, uint32, search.SearchTarget) error); ok { + r0 = returnFunc(ctx, id, target) } else { r0 = ret.Error(0) } - return r0 } -// SearchClient_Handle_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Handle' -type SearchClient_Handle_Call struct { +// SearchClient_EventAdded_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EventAdded' +type SearchClient_EventAdded_Call struct { *mock.Call } -// Handle is a helper method to define mock.On call -// - c echo.Context -func (_e *SearchClient_Expecter) Handle(c interface{}) *SearchClient_Handle_Call { - return &SearchClient_Handle_Call{Call: _e.mock.On("Handle", c)} +// EventAdded is a helper method to define mock.On call +// - ctx context.Context +// - id uint32 +// - target search.SearchTarget +func (_e *SearchClient_Expecter) EventAdded(ctx interface{}, id interface{}, target interface{}) *SearchClient_EventAdded_Call { + return &SearchClient_EventAdded_Call{Call: _e.mock.On("EventAdded", ctx, id, target)} } -func (_c *SearchClient_Handle_Call) Run(run func(c echo.Context)) *SearchClient_Handle_Call { +func (_c *SearchClient_EventAdded_Call) Run(run func(ctx context.Context, id uint32, target search.SearchTarget)) *SearchClient_EventAdded_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(echo.Context)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 uint32 + if args[1] != nil { + arg1 = args[1].(uint32) + } + var arg2 search.SearchTarget + if args[2] != nil { + arg2 = args[2].(search.SearchTarget) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } -func (_c *SearchClient_Handle_Call) Return(_a0 error) *SearchClient_Handle_Call { - _c.Call.Return(_a0) +func (_c *SearchClient_EventAdded_Call) Return(err error) *SearchClient_EventAdded_Call { + _c.Call.Return(err) return _c } -func (_c *SearchClient_Handle_Call) RunAndReturn(run func(echo.Context) error) *SearchClient_Handle_Call { +func (_c *SearchClient_EventAdded_Call) RunAndReturn(run func(ctx context.Context, id uint32, target search.SearchTarget) error) *SearchClient_EventAdded_Call { _c.Call.Return(run) return _c } -// OnSubjectDelete provides a mock function with given fields: ctx, id -func (_m *SearchClient) OnSubjectDelete(ctx context.Context, id uint32) error { - ret := _m.Called(ctx, id) +// EventDelete provides a mock function for the type SearchClient +func (_mock *SearchClient) EventDelete(ctx context.Context, id uint32, target search.SearchTarget) error { + ret := _mock.Called(ctx, id, target) + + if len(ret) == 0 { + panic("no return value specified for EventDelete") + } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) error); ok { - r0 = rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, uint32, search.SearchTarget) error); ok { + r0 = returnFunc(ctx, id, target) } else { r0 = ret.Error(0) } - return r0 } -// SearchClient_OnSubjectDelete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OnSubjectDelete' -type SearchClient_OnSubjectDelete_Call struct { +// SearchClient_EventDelete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EventDelete' +type SearchClient_EventDelete_Call struct { *mock.Call } -// OnSubjectDelete is a helper method to define mock.On call +// EventDelete is a helper method to define mock.On call // - ctx context.Context // - id uint32 -func (_e *SearchClient_Expecter) OnSubjectDelete(ctx interface{}, id interface{}) *SearchClient_OnSubjectDelete_Call { - return &SearchClient_OnSubjectDelete_Call{Call: _e.mock.On("OnSubjectDelete", ctx, id)} +// - target search.SearchTarget +func (_e *SearchClient_Expecter) EventDelete(ctx interface{}, id interface{}, target interface{}) *SearchClient_EventDelete_Call { + return &SearchClient_EventDelete_Call{Call: _e.mock.On("EventDelete", ctx, id, target)} } -func (_c *SearchClient_OnSubjectDelete_Call) Run(run func(ctx context.Context, id uint32)) *SearchClient_OnSubjectDelete_Call { +func (_c *SearchClient_EventDelete_Call) Run(run func(ctx context.Context, id uint32, target search.SearchTarget)) *SearchClient_EventDelete_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 uint32 + if args[1] != nil { + arg1 = args[1].(uint32) + } + var arg2 search.SearchTarget + if args[2] != nil { + arg2 = args[2].(search.SearchTarget) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } -func (_c *SearchClient_OnSubjectDelete_Call) Return(_a0 error) *SearchClient_OnSubjectDelete_Call { - _c.Call.Return(_a0) +func (_c *SearchClient_EventDelete_Call) Return(err error) *SearchClient_EventDelete_Call { + _c.Call.Return(err) return _c } -func (_c *SearchClient_OnSubjectDelete_Call) RunAndReturn(run func(context.Context, uint32) error) *SearchClient_OnSubjectDelete_Call { +func (_c *SearchClient_EventDelete_Call) RunAndReturn(run func(ctx context.Context, id uint32, target search.SearchTarget) error) *SearchClient_EventDelete_Call { _c.Call.Return(run) return _c } -// OnSubjectUpdate provides a mock function with given fields: ctx, id -func (_m *SearchClient) OnSubjectUpdate(ctx context.Context, id uint32) error { - ret := _m.Called(ctx, id) +// EventUpdate provides a mock function for the type SearchClient +func (_mock *SearchClient) EventUpdate(ctx context.Context, id uint32, target search.SearchTarget) error { + ret := _mock.Called(ctx, id, target) + + if len(ret) == 0 { + panic("no return value specified for EventUpdate") + } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) error); ok { - r0 = rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, uint32, search.SearchTarget) error); ok { + r0 = returnFunc(ctx, id, target) } else { r0 = ret.Error(0) } - return r0 } -// SearchClient_OnSubjectUpdate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OnSubjectUpdate' -type SearchClient_OnSubjectUpdate_Call struct { +// SearchClient_EventUpdate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EventUpdate' +type SearchClient_EventUpdate_Call struct { *mock.Call } -// OnSubjectUpdate is a helper method to define mock.On call +// EventUpdate is a helper method to define mock.On call // - ctx context.Context // - id uint32 -func (_e *SearchClient_Expecter) OnSubjectUpdate(ctx interface{}, id interface{}) *SearchClient_OnSubjectUpdate_Call { - return &SearchClient_OnSubjectUpdate_Call{Call: _e.mock.On("OnSubjectUpdate", ctx, id)} +// - target search.SearchTarget +func (_e *SearchClient_Expecter) EventUpdate(ctx interface{}, id interface{}, target interface{}) *SearchClient_EventUpdate_Call { + return &SearchClient_EventUpdate_Call{Call: _e.mock.On("EventUpdate", ctx, id, target)} } -func (_c *SearchClient_OnSubjectUpdate_Call) Run(run func(ctx context.Context, id uint32)) *SearchClient_OnSubjectUpdate_Call { +func (_c *SearchClient_EventUpdate_Call) Run(run func(ctx context.Context, id uint32, target search.SearchTarget)) *SearchClient_EventUpdate_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 uint32 + if args[1] != nil { + arg1 = args[1].(uint32) + } + var arg2 search.SearchTarget + if args[2] != nil { + arg2 = args[2].(search.SearchTarget) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } -func (_c *SearchClient_OnSubjectUpdate_Call) Return(_a0 error) *SearchClient_OnSubjectUpdate_Call { - _c.Call.Return(_a0) +func (_c *SearchClient_EventUpdate_Call) Return(err error) *SearchClient_EventUpdate_Call { + _c.Call.Return(err) return _c } -func (_c *SearchClient_OnSubjectUpdate_Call) RunAndReturn(run func(context.Context, uint32) error) *SearchClient_OnSubjectUpdate_Call { +func (_c *SearchClient_EventUpdate_Call) RunAndReturn(run func(ctx context.Context, id uint32, target search.SearchTarget) error) *SearchClient_EventUpdate_Call { _c.Call.Return(run) return _c } -type mockConstructorTestingTNewSearchClient interface { - mock.TestingT - Cleanup(func()) +// Handle provides a mock function for the type SearchClient +func (_mock *SearchClient) Handle(c *echo.Context, target search.SearchTarget) error { + ret := _mock.Called(c, target) + + if len(ret) == 0 { + panic("no return value specified for Handle") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(*echo.Context, search.SearchTarget) error); ok { + r0 = returnFunc(c, target) + } else { + r0 = ret.Error(0) + } + return r0 } -// NewSearchClient creates a new instance of SearchClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewSearchClient(t mockConstructorTestingTNewSearchClient) *SearchClient { - mock := &SearchClient{} - mock.Mock.Test(t) +// SearchClient_Handle_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Handle' +type SearchClient_Handle_Call struct { + *mock.Call +} - t.Cleanup(func() { mock.AssertExpectations(t) }) +// Handle is a helper method to define mock.On call +// - c *echo.Context +// - target search.SearchTarget +func (_e *SearchClient_Expecter) Handle(c interface{}, target interface{}) *SearchClient_Handle_Call { + return &SearchClient_Handle_Call{Call: _e.mock.On("Handle", c, target)} +} - return mock +func (_c *SearchClient_Handle_Call) Run(run func(c *echo.Context, target search.SearchTarget)) *SearchClient_Handle_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 *echo.Context + if args[0] != nil { + arg0 = args[0].(*echo.Context) + } + var arg1 search.SearchTarget + if args[1] != nil { + arg1 = args[1].(search.SearchTarget) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *SearchClient_Handle_Call) Return(err error) *SearchClient_Handle_Call { + _c.Call.Return(err) + return _c +} + +func (_c *SearchClient_Handle_Call) RunAndReturn(run func(c *echo.Context, target search.SearchTarget) error) *SearchClient_Handle_Call { + _c.Call.Return(run) + return _c } diff --git a/internal/mocks/SessionManager.go b/internal/mocks/SessionManager.go index 0ea6a4d93..7fe9befe8 100644 --- a/internal/mocks/SessionManager.go +++ b/internal/mocks/SessionManager.go @@ -1,17 +1,32 @@ -// Code generated by mockery v2.23.1. DO NOT EDIT. +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify package mocks import ( - context "context" - - auth "github.com/bangumi/server/internal/auth" + "context" + "github.com/bangumi/server/internal/auth" + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/web/session" mock "github.com/stretchr/testify/mock" - - session "github.com/bangumi/server/web/session" ) +// NewSessionManager creates a new instance of SessionManager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewSessionManager(t interface { + mock.TestingT + Cleanup(func()) +}) *SessionManager { + mock := &SessionManager{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + // SessionManager is an autogenerated mock type for the Manager type type SessionManager struct { mock.Mock @@ -25,34 +40,35 @@ func (_m *SessionManager) EXPECT() *SessionManager_Expecter { return &SessionManager_Expecter{mock: &_m.Mock} } -// Create provides a mock function with given fields: ctx, a -func (_m *SessionManager) Create(ctx context.Context, a auth.Auth) (string, session.Session, error) { - ret := _m.Called(ctx, a) +// Create provides a mock function for the type SessionManager +func (_mock *SessionManager) Create(ctx context.Context, a auth.Auth) (string, session.Session, error) { + ret := _mock.Called(ctx, a) + + if len(ret) == 0 { + panic("no return value specified for Create") + } var r0 string var r1 session.Session var r2 error - if rf, ok := ret.Get(0).(func(context.Context, auth.Auth) (string, session.Session, error)); ok { - return rf(ctx, a) + if returnFunc, ok := ret.Get(0).(func(context.Context, auth.Auth) (string, session.Session, error)); ok { + return returnFunc(ctx, a) } - if rf, ok := ret.Get(0).(func(context.Context, auth.Auth) string); ok { - r0 = rf(ctx, a) + if returnFunc, ok := ret.Get(0).(func(context.Context, auth.Auth) string); ok { + r0 = returnFunc(ctx, a) } else { r0 = ret.Get(0).(string) } - - if rf, ok := ret.Get(1).(func(context.Context, auth.Auth) session.Session); ok { - r1 = rf(ctx, a) + if returnFunc, ok := ret.Get(1).(func(context.Context, auth.Auth) session.Session); ok { + r1 = returnFunc(ctx, a) } else { r1 = ret.Get(1).(session.Session) } - - if rf, ok := ret.Get(2).(func(context.Context, auth.Auth) error); ok { - r2 = rf(ctx, a) + if returnFunc, ok := ret.Get(2).(func(context.Context, auth.Auth) error); ok { + r2 = returnFunc(ctx, a) } else { r2 = ret.Error(2) } - return r0, r1, r2 } @@ -70,42 +86,55 @@ func (_e *SessionManager_Expecter) Create(ctx interface{}, a interface{}) *Sessi func (_c *SessionManager_Create_Call) Run(run func(ctx context.Context, a auth.Auth)) *SessionManager_Create_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(auth.Auth)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 auth.Auth + if args[1] != nil { + arg1 = args[1].(auth.Auth) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *SessionManager_Create_Call) Return(_a0 string, _a1 session.Session, _a2 error) *SessionManager_Create_Call { - _c.Call.Return(_a0, _a1, _a2) +func (_c *SessionManager_Create_Call) Return(s string, session1 session.Session, err error) *SessionManager_Create_Call { + _c.Call.Return(s, session1, err) return _c } -func (_c *SessionManager_Create_Call) RunAndReturn(run func(context.Context, auth.Auth) (string, session.Session, error)) *SessionManager_Create_Call { +func (_c *SessionManager_Create_Call) RunAndReturn(run func(ctx context.Context, a auth.Auth) (string, session.Session, error)) *SessionManager_Create_Call { _c.Call.Return(run) return _c } -// Get provides a mock function with given fields: ctx, key -func (_m *SessionManager) Get(ctx context.Context, key string) (session.Session, error) { - ret := _m.Called(ctx, key) +// Get provides a mock function for the type SessionManager +func (_mock *SessionManager) Get(ctx context.Context, key string) (session.Session, error) { + ret := _mock.Called(ctx, key) + + if len(ret) == 0 { + panic("no return value specified for Get") + } var r0 session.Session var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (session.Session, error)); ok { - return rf(ctx, key) + if returnFunc, ok := ret.Get(0).(func(context.Context, string) (session.Session, error)); ok { + return returnFunc(ctx, key) } - if rf, ok := ret.Get(0).(func(context.Context, string) session.Session); ok { - r0 = rf(ctx, key) + if returnFunc, ok := ret.Get(0).(func(context.Context, string) session.Session); ok { + r0 = returnFunc(ctx, key) } else { r0 = ret.Get(0).(session.Session) } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, key) + if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = returnFunc(ctx, key) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -123,32 +152,46 @@ func (_e *SessionManager_Expecter) Get(ctx interface{}, key interface{}) *Sessio func (_c *SessionManager_Get_Call) Run(run func(ctx context.Context, key string)) *SessionManager_Get_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *SessionManager_Get_Call) Return(_a0 session.Session, _a1 error) *SessionManager_Get_Call { - _c.Call.Return(_a0, _a1) +func (_c *SessionManager_Get_Call) Return(session1 session.Session, err error) *SessionManager_Get_Call { + _c.Call.Return(session1, err) return _c } -func (_c *SessionManager_Get_Call) RunAndReturn(run func(context.Context, string) (session.Session, error)) *SessionManager_Get_Call { +func (_c *SessionManager_Get_Call) RunAndReturn(run func(ctx context.Context, key string) (session.Session, error)) *SessionManager_Get_Call { _c.Call.Return(run) return _c } -// Revoke provides a mock function with given fields: ctx, key -func (_m *SessionManager) Revoke(ctx context.Context, key string) error { - ret := _m.Called(ctx, key) +// Revoke provides a mock function for the type SessionManager +func (_mock *SessionManager) Revoke(ctx context.Context, key string) error { + ret := _mock.Called(ctx, key) + + if len(ret) == 0 { + panic("no return value specified for Revoke") + } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, key) + if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = returnFunc(ctx, key) } else { r0 = ret.Error(0) } - return r0 } @@ -166,32 +209,46 @@ func (_e *SessionManager_Expecter) Revoke(ctx interface{}, key interface{}) *Ses func (_c *SessionManager_Revoke_Call) Run(run func(ctx context.Context, key string)) *SessionManager_Revoke_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *SessionManager_Revoke_Call) Return(_a0 error) *SessionManager_Revoke_Call { - _c.Call.Return(_a0) +func (_c *SessionManager_Revoke_Call) Return(err error) *SessionManager_Revoke_Call { + _c.Call.Return(err) return _c } -func (_c *SessionManager_Revoke_Call) RunAndReturn(run func(context.Context, string) error) *SessionManager_Revoke_Call { +func (_c *SessionManager_Revoke_Call) RunAndReturn(run func(ctx context.Context, key string) error) *SessionManager_Revoke_Call { _c.Call.Return(run) return _c } -// RevokeUser provides a mock function with given fields: ctx, id -func (_m *SessionManager) RevokeUser(ctx context.Context, id uint32) error { - ret := _m.Called(ctx, id) +// RevokeUser provides a mock function for the type SessionManager +func (_mock *SessionManager) RevokeUser(ctx context.Context, id model.UserID) error { + ret := _mock.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for RevokeUser") + } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) error); ok { - r0 = rf(ctx, id) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID) error); ok { + r0 = returnFunc(ctx, id) } else { r0 = ret.Error(0) } - return r0 } @@ -202,39 +259,35 @@ type SessionManager_RevokeUser_Call struct { // RevokeUser is a helper method to define mock.On call // - ctx context.Context -// - id uint32 +// - id model.UserID func (_e *SessionManager_Expecter) RevokeUser(ctx interface{}, id interface{}) *SessionManager_RevokeUser_Call { return &SessionManager_RevokeUser_Call{Call: _e.mock.On("RevokeUser", ctx, id)} } -func (_c *SessionManager_RevokeUser_Call) Run(run func(ctx context.Context, id uint32)) *SessionManager_RevokeUser_Call { +func (_c *SessionManager_RevokeUser_Call) Run(run func(ctx context.Context, id model.UserID)) *SessionManager_RevokeUser_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.UserID + if args[1] != nil { + arg1 = args[1].(model.UserID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *SessionManager_RevokeUser_Call) Return(_a0 error) *SessionManager_RevokeUser_Call { - _c.Call.Return(_a0) +func (_c *SessionManager_RevokeUser_Call) Return(err error) *SessionManager_RevokeUser_Call { + _c.Call.Return(err) return _c } -func (_c *SessionManager_RevokeUser_Call) RunAndReturn(run func(context.Context, uint32) error) *SessionManager_RevokeUser_Call { +func (_c *SessionManager_RevokeUser_Call) RunAndReturn(run func(ctx context.Context, id model.UserID) error) *SessionManager_RevokeUser_Call { _c.Call.Return(run) return _c } - -type mockConstructorTestingTNewSessionManager interface { - mock.TestingT - Cleanup(func()) -} - -// NewSessionManager creates a new instance of SessionManager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewSessionManager(t mockConstructorTestingTNewSessionManager) *SessionManager { - mock := &SessionManager{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/mocks/SessionRepo.go b/internal/mocks/SessionRepo.go index 9c30fc0f4..7f21e6d28 100644 --- a/internal/mocks/SessionRepo.go +++ b/internal/mocks/SessionRepo.go @@ -1,16 +1,32 @@ -// Code generated by mockery v2.23.1. DO NOT EDIT. +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify package mocks import ( - context "context" + "context" + "time" - session "github.com/bangumi/server/web/session" + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/web/session" mock "github.com/stretchr/testify/mock" - - time "time" ) +// NewSessionRepo creates a new instance of SessionRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewSessionRepo(t interface { + mock.TestingT + Cleanup(func()) +}) *SessionRepo { + mock := &SessionRepo{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + // SessionRepo is an autogenerated mock type for the Repo type type SessionRepo struct { mock.Mock @@ -24,34 +40,35 @@ func (_m *SessionRepo) EXPECT() *SessionRepo_Expecter { return &SessionRepo_Expecter{mock: &_m.Mock} } -// Create provides a mock function with given fields: ctx, userID, regTime, keyGen -func (_m *SessionRepo) Create(ctx context.Context, userID uint32, regTime time.Time, keyGen func() string) (string, session.Session, error) { - ret := _m.Called(ctx, userID, regTime, keyGen) +// Create provides a mock function for the type SessionRepo +func (_mock *SessionRepo) Create(ctx context.Context, userID model.UserID, regTime time.Time, keyGen func() string) (string, session.Session, error) { + ret := _mock.Called(ctx, userID, regTime, keyGen) + + if len(ret) == 0 { + panic("no return value specified for Create") + } var r0 string var r1 session.Session var r2 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, time.Time, func() string) (string, session.Session, error)); ok { - return rf(ctx, userID, regTime, keyGen) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, time.Time, func() string) (string, session.Session, error)); ok { + return returnFunc(ctx, userID, regTime, keyGen) } - if rf, ok := ret.Get(0).(func(context.Context, uint32, time.Time, func() string) string); ok { - r0 = rf(ctx, userID, regTime, keyGen) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, time.Time, func() string) string); ok { + r0 = returnFunc(ctx, userID, regTime, keyGen) } else { r0 = ret.Get(0).(string) } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, time.Time, func() string) session.Session); ok { - r1 = rf(ctx, userID, regTime, keyGen) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.UserID, time.Time, func() string) session.Session); ok { + r1 = returnFunc(ctx, userID, regTime, keyGen) } else { r1 = ret.Get(1).(session.Session) } - - if rf, ok := ret.Get(2).(func(context.Context, uint32, time.Time, func() string) error); ok { - r2 = rf(ctx, userID, regTime, keyGen) + if returnFunc, ok := ret.Get(2).(func(context.Context, model.UserID, time.Time, func() string) error); ok { + r2 = returnFunc(ctx, userID, regTime, keyGen) } else { r2 = ret.Error(2) } - return r0, r1, r2 } @@ -62,16 +79,37 @@ type SessionRepo_Create_Call struct { // Create is a helper method to define mock.On call // - ctx context.Context -// - userID uint32 +// - userID model.UserID // - regTime time.Time // - keyGen func() string func (_e *SessionRepo_Expecter) Create(ctx interface{}, userID interface{}, regTime interface{}, keyGen interface{}) *SessionRepo_Create_Call { return &SessionRepo_Create_Call{Call: _e.mock.On("Create", ctx, userID, regTime, keyGen)} } -func (_c *SessionRepo_Create_Call) Run(run func(ctx context.Context, userID uint32, regTime time.Time, keyGen func() string)) *SessionRepo_Create_Call { +func (_c *SessionRepo_Create_Call) Run(run func(ctx context.Context, userID model.UserID, regTime time.Time, keyGen func() string)) *SessionRepo_Create_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(time.Time), args[3].(func() string)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.UserID + if args[1] != nil { + arg1 = args[1].(model.UserID) + } + var arg2 time.Time + if args[2] != nil { + arg2 = args[2].(time.Time) + } + var arg3 func() string + if args[3] != nil { + arg3 = args[3].(func() string) + } + run( + arg0, + arg1, + arg2, + arg3, + ) }) return _c } @@ -81,32 +119,34 @@ func (_c *SessionRepo_Create_Call) Return(key string, s session.Session, err err return _c } -func (_c *SessionRepo_Create_Call) RunAndReturn(run func(context.Context, uint32, time.Time, func() string) (string, session.Session, error)) *SessionRepo_Create_Call { +func (_c *SessionRepo_Create_Call) RunAndReturn(run func(ctx context.Context, userID model.UserID, regTime time.Time, keyGen func() string) (string, session.Session, error)) *SessionRepo_Create_Call { _c.Call.Return(run) return _c } -// Get provides a mock function with given fields: ctx, key -func (_m *SessionRepo) Get(ctx context.Context, key string) (session.Session, error) { - ret := _m.Called(ctx, key) +// Get provides a mock function for the type SessionRepo +func (_mock *SessionRepo) Get(ctx context.Context, key string) (session.Session, error) { + ret := _mock.Called(ctx, key) + + if len(ret) == 0 { + panic("no return value specified for Get") + } var r0 session.Session var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (session.Session, error)); ok { - return rf(ctx, key) + if returnFunc, ok := ret.Get(0).(func(context.Context, string) (session.Session, error)); ok { + return returnFunc(ctx, key) } - if rf, ok := ret.Get(0).(func(context.Context, string) session.Session); ok { - r0 = rf(ctx, key) + if returnFunc, ok := ret.Get(0).(func(context.Context, string) session.Session); ok { + r0 = returnFunc(ctx, key) } else { r0 = ret.Get(0).(session.Session) } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, key) + if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = returnFunc(ctx, key) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -124,32 +164,46 @@ func (_e *SessionRepo_Expecter) Get(ctx interface{}, key interface{}) *SessionRe func (_c *SessionRepo_Get_Call) Run(run func(ctx context.Context, key string)) *SessionRepo_Get_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *SessionRepo_Get_Call) Return(_a0 session.Session, _a1 error) *SessionRepo_Get_Call { - _c.Call.Return(_a0, _a1) +func (_c *SessionRepo_Get_Call) Return(session1 session.Session, err error) *SessionRepo_Get_Call { + _c.Call.Return(session1, err) return _c } -func (_c *SessionRepo_Get_Call) RunAndReturn(run func(context.Context, string) (session.Session, error)) *SessionRepo_Get_Call { +func (_c *SessionRepo_Get_Call) RunAndReturn(run func(ctx context.Context, key string) (session.Session, error)) *SessionRepo_Get_Call { _c.Call.Return(run) return _c } -// Revoke provides a mock function with given fields: ctx, key -func (_m *SessionRepo) Revoke(ctx context.Context, key string) error { - ret := _m.Called(ctx, key) +// Revoke provides a mock function for the type SessionRepo +func (_mock *SessionRepo) Revoke(ctx context.Context, key string) error { + ret := _mock.Called(ctx, key) + + if len(ret) == 0 { + panic("no return value specified for Revoke") + } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, key) + if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = returnFunc(ctx, key) } else { r0 = ret.Error(0) } - return r0 } @@ -167,44 +221,57 @@ func (_e *SessionRepo_Expecter) Revoke(ctx interface{}, key interface{}) *Sessio func (_c *SessionRepo_Revoke_Call) Run(run func(ctx context.Context, key string)) *SessionRepo_Revoke_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *SessionRepo_Revoke_Call) Return(_a0 error) *SessionRepo_Revoke_Call { - _c.Call.Return(_a0) +func (_c *SessionRepo_Revoke_Call) Return(err error) *SessionRepo_Revoke_Call { + _c.Call.Return(err) return _c } -func (_c *SessionRepo_Revoke_Call) RunAndReturn(run func(context.Context, string) error) *SessionRepo_Revoke_Call { +func (_c *SessionRepo_Revoke_Call) RunAndReturn(run func(ctx context.Context, key string) error) *SessionRepo_Revoke_Call { _c.Call.Return(run) return _c } -// RevokeUser provides a mock function with given fields: ctx, userID -func (_m *SessionRepo) RevokeUser(ctx context.Context, userID uint32) ([]string, error) { - ret := _m.Called(ctx, userID) +// RevokeUser provides a mock function for the type SessionRepo +func (_mock *SessionRepo) RevokeUser(ctx context.Context, userID model.UserID) ([]string, error) { + ret := _mock.Called(ctx, userID) + + if len(ret) == 0 { + panic("no return value specified for RevokeUser") + } var r0 []string var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) ([]string, error)); ok { - return rf(ctx, userID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID) ([]string, error)); ok { + return returnFunc(ctx, userID) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) []string); ok { - r0 = rf(ctx, userID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID) []string); ok { + r0 = returnFunc(ctx, userID) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]string) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, userID) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.UserID) error); ok { + r1 = returnFunc(ctx, userID) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -215,14 +282,25 @@ type SessionRepo_RevokeUser_Call struct { // RevokeUser is a helper method to define mock.On call // - ctx context.Context -// - userID uint32 +// - userID model.UserID func (_e *SessionRepo_Expecter) RevokeUser(ctx interface{}, userID interface{}) *SessionRepo_RevokeUser_Call { return &SessionRepo_RevokeUser_Call{Call: _e.mock.On("RevokeUser", ctx, userID)} } -func (_c *SessionRepo_RevokeUser_Call) Run(run func(ctx context.Context, userID uint32)) *SessionRepo_RevokeUser_Call { +func (_c *SessionRepo_RevokeUser_Call) Run(run func(ctx context.Context, userID model.UserID)) *SessionRepo_RevokeUser_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.UserID + if args[1] != nil { + arg1 = args[1].(model.UserID) + } + run( + arg0, + arg1, + ) }) return _c } @@ -232,22 +310,7 @@ func (_c *SessionRepo_RevokeUser_Call) Return(keys []string, err error) *Session return _c } -func (_c *SessionRepo_RevokeUser_Call) RunAndReturn(run func(context.Context, uint32) ([]string, error)) *SessionRepo_RevokeUser_Call { +func (_c *SessionRepo_RevokeUser_Call) RunAndReturn(run func(ctx context.Context, userID model.UserID) ([]string, error)) *SessionRepo_RevokeUser_Call { _c.Call.Return(run) return _c } - -type mockConstructorTestingTNewSessionRepo interface { - mock.TestingT - Cleanup(func()) -} - -// NewSessionRepo creates a new instance of SessionRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewSessionRepo(t mockConstructorTestingTNewSessionRepo) *SessionRepo { - mock := &SessionRepo{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/mocks/SubjectCachedRepo.go b/internal/mocks/SubjectCachedRepo.go index 0127aaf85..0c2d9c9f1 100644 --- a/internal/mocks/SubjectCachedRepo.go +++ b/internal/mocks/SubjectCachedRepo.go @@ -1,17 +1,31 @@ -// Code generated by mockery v2.23.1. DO NOT EDIT. +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify package mocks import ( - context "context" + "context" - domain "github.com/bangumi/server/domain" + "github.com/bangumi/server/domain" + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/subject" mock "github.com/stretchr/testify/mock" +) - model "github.com/bangumi/server/internal/model" +// NewSubjectCachedRepo creates a new instance of SubjectCachedRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewSubjectCachedRepo(t interface { + mock.TestingT + Cleanup(func()) +}) *SubjectCachedRepo { + mock := &SubjectCachedRepo{} + mock.Mock.Test(t) - subject "github.com/bangumi/server/internal/subject" -) + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} // SubjectCachedRepo is an autogenerated mock type for the CachedRepo type type SubjectCachedRepo struct { @@ -26,27 +40,175 @@ func (_m *SubjectCachedRepo) EXPECT() *SubjectCachedRepo_Expecter { return &SubjectCachedRepo_Expecter{mock: &_m.Mock} } -// Get provides a mock function with given fields: ctx, id, filter -func (_m *SubjectCachedRepo) Get(ctx context.Context, id uint32, filter subject.Filter) (model.Subject, error) { - ret := _m.Called(ctx, id, filter) +// Browse provides a mock function for the type SubjectCachedRepo +func (_mock *SubjectCachedRepo) Browse(ctx context.Context, filter subject.BrowseFilter, limit int, offset int) ([]model.Subject, error) { + ret := _mock.Called(ctx, filter, limit, offset) - var r0 model.Subject + if len(ret) == 0 { + panic("no return value specified for Browse") + } + + var r0 []model.Subject var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, subject.Filter) (model.Subject, error)); ok { - return rf(ctx, id, filter) + if returnFunc, ok := ret.Get(0).(func(context.Context, subject.BrowseFilter, int, int) ([]model.Subject, error)); ok { + return returnFunc(ctx, filter, limit, offset) } - if rf, ok := ret.Get(0).(func(context.Context, uint32, subject.Filter) model.Subject); ok { - r0 = rf(ctx, id, filter) + if returnFunc, ok := ret.Get(0).(func(context.Context, subject.BrowseFilter, int, int) []model.Subject); ok { + r0 = returnFunc(ctx, filter, limit, offset) } else { - r0 = ret.Get(0).(model.Subject) + if ret.Get(0) != nil { + r0 = ret.Get(0).([]model.Subject) + } } + if returnFunc, ok := ret.Get(1).(func(context.Context, subject.BrowseFilter, int, int) error); ok { + r1 = returnFunc(ctx, filter, limit, offset) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} - if rf, ok := ret.Get(1).(func(context.Context, uint32, subject.Filter) error); ok { - r1 = rf(ctx, id, filter) +// SubjectCachedRepo_Browse_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Browse' +type SubjectCachedRepo_Browse_Call struct { + *mock.Call +} + +// Browse is a helper method to define mock.On call +// - ctx context.Context +// - filter subject.BrowseFilter +// - limit int +// - offset int +func (_e *SubjectCachedRepo_Expecter) Browse(ctx interface{}, filter interface{}, limit interface{}, offset interface{}) *SubjectCachedRepo_Browse_Call { + return &SubjectCachedRepo_Browse_Call{Call: _e.mock.On("Browse", ctx, filter, limit, offset)} +} + +func (_c *SubjectCachedRepo_Browse_Call) Run(run func(ctx context.Context, filter subject.BrowseFilter, limit int, offset int)) *SubjectCachedRepo_Browse_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 subject.BrowseFilter + if args[1] != nil { + arg1 = args[1].(subject.BrowseFilter) + } + var arg2 int + if args[2] != nil { + arg2 = args[2].(int) + } + var arg3 int + if args[3] != nil { + arg3 = args[3].(int) + } + run( + arg0, + arg1, + arg2, + arg3, + ) + }) + return _c +} + +func (_c *SubjectCachedRepo_Browse_Call) Return(subjects []model.Subject, err error) *SubjectCachedRepo_Browse_Call { + _c.Call.Return(subjects, err) + return _c +} + +func (_c *SubjectCachedRepo_Browse_Call) RunAndReturn(run func(ctx context.Context, filter subject.BrowseFilter, limit int, offset int) ([]model.Subject, error)) *SubjectCachedRepo_Browse_Call { + _c.Call.Return(run) + return _c +} + +// Count provides a mock function for the type SubjectCachedRepo +func (_mock *SubjectCachedRepo) Count(ctx context.Context, filter subject.BrowseFilter) (int64, error) { + ret := _mock.Called(ctx, filter) + + if len(ret) == 0 { + panic("no return value specified for Count") + } + + var r0 int64 + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, subject.BrowseFilter) (int64, error)); ok { + return returnFunc(ctx, filter) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, subject.BrowseFilter) int64); ok { + r0 = returnFunc(ctx, filter) + } else { + r0 = ret.Get(0).(int64) + } + if returnFunc, ok := ret.Get(1).(func(context.Context, subject.BrowseFilter) error); ok { + r1 = returnFunc(ctx, filter) } else { r1 = ret.Error(1) } + return r0, r1 +} + +// SubjectCachedRepo_Count_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Count' +type SubjectCachedRepo_Count_Call struct { + *mock.Call +} + +// Count is a helper method to define mock.On call +// - ctx context.Context +// - filter subject.BrowseFilter +func (_e *SubjectCachedRepo_Expecter) Count(ctx interface{}, filter interface{}) *SubjectCachedRepo_Count_Call { + return &SubjectCachedRepo_Count_Call{Call: _e.mock.On("Count", ctx, filter)} +} +func (_c *SubjectCachedRepo_Count_Call) Run(run func(ctx context.Context, filter subject.BrowseFilter)) *SubjectCachedRepo_Count_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 subject.BrowseFilter + if args[1] != nil { + arg1 = args[1].(subject.BrowseFilter) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *SubjectCachedRepo_Count_Call) Return(n int64, err error) *SubjectCachedRepo_Count_Call { + _c.Call.Return(n, err) + return _c +} + +func (_c *SubjectCachedRepo_Count_Call) RunAndReturn(run func(ctx context.Context, filter subject.BrowseFilter) (int64, error)) *SubjectCachedRepo_Count_Call { + _c.Call.Return(run) + return _c +} + +// Get provides a mock function for the type SubjectCachedRepo +func (_mock *SubjectCachedRepo) Get(ctx context.Context, id model.SubjectID, filter subject.Filter) (model.Subject, error) { + ret := _mock.Called(ctx, id, filter) + + if len(ret) == 0 { + panic("no return value specified for Get") + } + + var r0 model.Subject + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID, subject.Filter) (model.Subject, error)); ok { + return returnFunc(ctx, id, filter) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID, subject.Filter) model.Subject); ok { + r0 = returnFunc(ctx, id, filter) + } else { + r0 = ret.Get(0).(model.Subject) + } + if returnFunc, ok := ret.Get(1).(func(context.Context, model.SubjectID, subject.Filter) error); ok { + r1 = returnFunc(ctx, id, filter) + } else { + r1 = ret.Error(1) + } return r0, r1 } @@ -57,52 +219,70 @@ type SubjectCachedRepo_Get_Call struct { // Get is a helper method to define mock.On call // - ctx context.Context -// - id uint32 +// - id model.SubjectID // - filter subject.Filter func (_e *SubjectCachedRepo_Expecter) Get(ctx interface{}, id interface{}, filter interface{}) *SubjectCachedRepo_Get_Call { return &SubjectCachedRepo_Get_Call{Call: _e.mock.On("Get", ctx, id, filter)} } -func (_c *SubjectCachedRepo_Get_Call) Run(run func(ctx context.Context, id uint32, filter subject.Filter)) *SubjectCachedRepo_Get_Call { +func (_c *SubjectCachedRepo_Get_Call) Run(run func(ctx context.Context, id model.SubjectID, filter subject.Filter)) *SubjectCachedRepo_Get_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(subject.Filter)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.SubjectID + if args[1] != nil { + arg1 = args[1].(model.SubjectID) + } + var arg2 subject.Filter + if args[2] != nil { + arg2 = args[2].(subject.Filter) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } -func (_c *SubjectCachedRepo_Get_Call) Return(_a0 model.Subject, _a1 error) *SubjectCachedRepo_Get_Call { - _c.Call.Return(_a0, _a1) +func (_c *SubjectCachedRepo_Get_Call) Return(subject1 model.Subject, err error) *SubjectCachedRepo_Get_Call { + _c.Call.Return(subject1, err) return _c } -func (_c *SubjectCachedRepo_Get_Call) RunAndReturn(run func(context.Context, uint32, subject.Filter) (model.Subject, error)) *SubjectCachedRepo_Get_Call { +func (_c *SubjectCachedRepo_Get_Call) RunAndReturn(run func(ctx context.Context, id model.SubjectID, filter subject.Filter) (model.Subject, error)) *SubjectCachedRepo_Get_Call { _c.Call.Return(run) return _c } -// GetActors provides a mock function with given fields: ctx, subjectID, characterIDs -func (_m *SubjectCachedRepo) GetActors(ctx context.Context, subjectID uint32, characterIDs []uint32) (map[uint32][]uint32, error) { - ret := _m.Called(ctx, subjectID, characterIDs) +// GetActors provides a mock function for the type SubjectCachedRepo +func (_mock *SubjectCachedRepo) GetActors(ctx context.Context, subjectID model.SubjectID, characterIDs []model.CharacterID) (map[model.CharacterID][]model.PersonID, error) { + ret := _mock.Called(ctx, subjectID, characterIDs) - var r0 map[uint32][]uint32 + if len(ret) == 0 { + panic("no return value specified for GetActors") + } + + var r0 map[model.CharacterID][]model.PersonID var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, []uint32) (map[uint32][]uint32, error)); ok { - return rf(ctx, subjectID, characterIDs) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID, []model.CharacterID) (map[model.CharacterID][]model.PersonID, error)); ok { + return returnFunc(ctx, subjectID, characterIDs) } - if rf, ok := ret.Get(0).(func(context.Context, uint32, []uint32) map[uint32][]uint32); ok { - r0 = rf(ctx, subjectID, characterIDs) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID, []model.CharacterID) map[model.CharacterID][]model.PersonID); ok { + r0 = returnFunc(ctx, subjectID, characterIDs) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(map[uint32][]uint32) + r0 = ret.Get(0).(map[model.CharacterID][]model.PersonID) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, []uint32) error); ok { - r1 = rf(ctx, subjectID, characterIDs) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.SubjectID, []model.CharacterID) error); ok { + r1 = returnFunc(ctx, subjectID, characterIDs) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -113,52 +293,70 @@ type SubjectCachedRepo_GetActors_Call struct { // GetActors is a helper method to define mock.On call // - ctx context.Context -// - subjectID uint32 -// - characterIDs []uint32 +// - subjectID model.SubjectID +// - characterIDs []model.CharacterID func (_e *SubjectCachedRepo_Expecter) GetActors(ctx interface{}, subjectID interface{}, characterIDs interface{}) *SubjectCachedRepo_GetActors_Call { return &SubjectCachedRepo_GetActors_Call{Call: _e.mock.On("GetActors", ctx, subjectID, characterIDs)} } -func (_c *SubjectCachedRepo_GetActors_Call) Run(run func(ctx context.Context, subjectID uint32, characterIDs []uint32)) *SubjectCachedRepo_GetActors_Call { +func (_c *SubjectCachedRepo_GetActors_Call) Run(run func(ctx context.Context, subjectID model.SubjectID, characterIDs []model.CharacterID)) *SubjectCachedRepo_GetActors_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].([]uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.SubjectID + if args[1] != nil { + arg1 = args[1].(model.SubjectID) + } + var arg2 []model.CharacterID + if args[2] != nil { + arg2 = args[2].([]model.CharacterID) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } -func (_c *SubjectCachedRepo_GetActors_Call) Return(_a0 map[uint32][]uint32, _a1 error) *SubjectCachedRepo_GetActors_Call { - _c.Call.Return(_a0, _a1) +func (_c *SubjectCachedRepo_GetActors_Call) Return(vToVs map[model.CharacterID][]model.PersonID, err error) *SubjectCachedRepo_GetActors_Call { + _c.Call.Return(vToVs, err) return _c } -func (_c *SubjectCachedRepo_GetActors_Call) RunAndReturn(run func(context.Context, uint32, []uint32) (map[uint32][]uint32, error)) *SubjectCachedRepo_GetActors_Call { +func (_c *SubjectCachedRepo_GetActors_Call) RunAndReturn(run func(ctx context.Context, subjectID model.SubjectID, characterIDs []model.CharacterID) (map[model.CharacterID][]model.PersonID, error)) *SubjectCachedRepo_GetActors_Call { _c.Call.Return(run) return _c } -// GetByIDs provides a mock function with given fields: ctx, ids, filter -func (_m *SubjectCachedRepo) GetByIDs(ctx context.Context, ids []uint32, filter subject.Filter) (map[uint32]model.Subject, error) { - ret := _m.Called(ctx, ids, filter) +// GetByIDs provides a mock function for the type SubjectCachedRepo +func (_mock *SubjectCachedRepo) GetByIDs(ctx context.Context, ids []model.SubjectID, filter subject.Filter) (map[model.SubjectID]model.Subject, error) { + ret := _mock.Called(ctx, ids, filter) + + if len(ret) == 0 { + panic("no return value specified for GetByIDs") + } - var r0 map[uint32]model.Subject + var r0 map[model.SubjectID]model.Subject var r1 error - if rf, ok := ret.Get(0).(func(context.Context, []uint32, subject.Filter) (map[uint32]model.Subject, error)); ok { - return rf(ctx, ids, filter) + if returnFunc, ok := ret.Get(0).(func(context.Context, []model.SubjectID, subject.Filter) (map[model.SubjectID]model.Subject, error)); ok { + return returnFunc(ctx, ids, filter) } - if rf, ok := ret.Get(0).(func(context.Context, []uint32, subject.Filter) map[uint32]model.Subject); ok { - r0 = rf(ctx, ids, filter) + if returnFunc, ok := ret.Get(0).(func(context.Context, []model.SubjectID, subject.Filter) map[model.SubjectID]model.Subject); ok { + r0 = returnFunc(ctx, ids, filter) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(map[uint32]model.Subject) + r0 = ret.Get(0).(map[model.SubjectID]model.Subject) } } - - if rf, ok := ret.Get(1).(func(context.Context, []uint32, subject.Filter) error); ok { - r1 = rf(ctx, ids, filter) + if returnFunc, ok := ret.Get(1).(func(context.Context, []model.SubjectID, subject.Filter) error); ok { + r1 = returnFunc(ctx, ids, filter) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -169,52 +367,70 @@ type SubjectCachedRepo_GetByIDs_Call struct { // GetByIDs is a helper method to define mock.On call // - ctx context.Context -// - ids []uint32 +// - ids []model.SubjectID // - filter subject.Filter func (_e *SubjectCachedRepo_Expecter) GetByIDs(ctx interface{}, ids interface{}, filter interface{}) *SubjectCachedRepo_GetByIDs_Call { return &SubjectCachedRepo_GetByIDs_Call{Call: _e.mock.On("GetByIDs", ctx, ids, filter)} } -func (_c *SubjectCachedRepo_GetByIDs_Call) Run(run func(ctx context.Context, ids []uint32, filter subject.Filter)) *SubjectCachedRepo_GetByIDs_Call { +func (_c *SubjectCachedRepo_GetByIDs_Call) Run(run func(ctx context.Context, ids []model.SubjectID, filter subject.Filter)) *SubjectCachedRepo_GetByIDs_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].([]uint32), args[2].(subject.Filter)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []model.SubjectID + if args[1] != nil { + arg1 = args[1].([]model.SubjectID) + } + var arg2 subject.Filter + if args[2] != nil { + arg2 = args[2].(subject.Filter) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } -func (_c *SubjectCachedRepo_GetByIDs_Call) Return(_a0 map[uint32]model.Subject, _a1 error) *SubjectCachedRepo_GetByIDs_Call { - _c.Call.Return(_a0, _a1) +func (_c *SubjectCachedRepo_GetByIDs_Call) Return(vToSubject map[model.SubjectID]model.Subject, err error) *SubjectCachedRepo_GetByIDs_Call { + _c.Call.Return(vToSubject, err) return _c } -func (_c *SubjectCachedRepo_GetByIDs_Call) RunAndReturn(run func(context.Context, []uint32, subject.Filter) (map[uint32]model.Subject, error)) *SubjectCachedRepo_GetByIDs_Call { +func (_c *SubjectCachedRepo_GetByIDs_Call) RunAndReturn(run func(ctx context.Context, ids []model.SubjectID, filter subject.Filter) (map[model.SubjectID]model.Subject, error)) *SubjectCachedRepo_GetByIDs_Call { _c.Call.Return(run) return _c } -// GetCharacterRelated provides a mock function with given fields: ctx, characterID -func (_m *SubjectCachedRepo) GetCharacterRelated(ctx context.Context, characterID uint32) ([]domain.SubjectCharacterRelation, error) { - ret := _m.Called(ctx, characterID) +// GetCharacterRelated provides a mock function for the type SubjectCachedRepo +func (_mock *SubjectCachedRepo) GetCharacterRelated(ctx context.Context, characterID model.CharacterID) ([]domain.SubjectCharacterRelation, error) { + ret := _mock.Called(ctx, characterID) + + if len(ret) == 0 { + panic("no return value specified for GetCharacterRelated") + } var r0 []domain.SubjectCharacterRelation var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) ([]domain.SubjectCharacterRelation, error)); ok { - return rf(ctx, characterID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.CharacterID) ([]domain.SubjectCharacterRelation, error)); ok { + return returnFunc(ctx, characterID) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) []domain.SubjectCharacterRelation); ok { - r0 = rf(ctx, characterID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.CharacterID) []domain.SubjectCharacterRelation); ok { + r0 = returnFunc(ctx, characterID) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]domain.SubjectCharacterRelation) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, characterID) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.CharacterID) error); ok { + r1 = returnFunc(ctx, characterID) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -225,51 +441,64 @@ type SubjectCachedRepo_GetCharacterRelated_Call struct { // GetCharacterRelated is a helper method to define mock.On call // - ctx context.Context -// - characterID uint32 +// - characterID model.CharacterID func (_e *SubjectCachedRepo_Expecter) GetCharacterRelated(ctx interface{}, characterID interface{}) *SubjectCachedRepo_GetCharacterRelated_Call { return &SubjectCachedRepo_GetCharacterRelated_Call{Call: _e.mock.On("GetCharacterRelated", ctx, characterID)} } -func (_c *SubjectCachedRepo_GetCharacterRelated_Call) Run(run func(ctx context.Context, characterID uint32)) *SubjectCachedRepo_GetCharacterRelated_Call { +func (_c *SubjectCachedRepo_GetCharacterRelated_Call) Run(run func(ctx context.Context, characterID model.CharacterID)) *SubjectCachedRepo_GetCharacterRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.CharacterID + if args[1] != nil { + arg1 = args[1].(model.CharacterID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *SubjectCachedRepo_GetCharacterRelated_Call) Return(_a0 []domain.SubjectCharacterRelation, _a1 error) *SubjectCachedRepo_GetCharacterRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *SubjectCachedRepo_GetCharacterRelated_Call) Return(subjectCharacterRelations []domain.SubjectCharacterRelation, err error) *SubjectCachedRepo_GetCharacterRelated_Call { + _c.Call.Return(subjectCharacterRelations, err) return _c } -func (_c *SubjectCachedRepo_GetCharacterRelated_Call) RunAndReturn(run func(context.Context, uint32) ([]domain.SubjectCharacterRelation, error)) *SubjectCachedRepo_GetCharacterRelated_Call { +func (_c *SubjectCachedRepo_GetCharacterRelated_Call) RunAndReturn(run func(ctx context.Context, characterID model.CharacterID) ([]domain.SubjectCharacterRelation, error)) *SubjectCachedRepo_GetCharacterRelated_Call { _c.Call.Return(run) return _c } -// GetPersonRelated provides a mock function with given fields: ctx, personID -func (_m *SubjectCachedRepo) GetPersonRelated(ctx context.Context, personID uint32) ([]domain.SubjectPersonRelation, error) { - ret := _m.Called(ctx, personID) +// GetPersonRelated provides a mock function for the type SubjectCachedRepo +func (_mock *SubjectCachedRepo) GetPersonRelated(ctx context.Context, personID model.PersonID) ([]domain.SubjectPersonRelation, error) { + ret := _mock.Called(ctx, personID) + + if len(ret) == 0 { + panic("no return value specified for GetPersonRelated") + } var r0 []domain.SubjectPersonRelation var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) ([]domain.SubjectPersonRelation, error)); ok { - return rf(ctx, personID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.PersonID) ([]domain.SubjectPersonRelation, error)); ok { + return returnFunc(ctx, personID) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) []domain.SubjectPersonRelation); ok { - r0 = rf(ctx, personID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.PersonID) []domain.SubjectPersonRelation); ok { + r0 = returnFunc(ctx, personID) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]domain.SubjectPersonRelation) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, personID) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.PersonID) error); ok { + r1 = returnFunc(ctx, personID) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -280,51 +509,64 @@ type SubjectCachedRepo_GetPersonRelated_Call struct { // GetPersonRelated is a helper method to define mock.On call // - ctx context.Context -// - personID uint32 +// - personID model.PersonID func (_e *SubjectCachedRepo_Expecter) GetPersonRelated(ctx interface{}, personID interface{}) *SubjectCachedRepo_GetPersonRelated_Call { return &SubjectCachedRepo_GetPersonRelated_Call{Call: _e.mock.On("GetPersonRelated", ctx, personID)} } -func (_c *SubjectCachedRepo_GetPersonRelated_Call) Run(run func(ctx context.Context, personID uint32)) *SubjectCachedRepo_GetPersonRelated_Call { +func (_c *SubjectCachedRepo_GetPersonRelated_Call) Run(run func(ctx context.Context, personID model.PersonID)) *SubjectCachedRepo_GetPersonRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.PersonID + if args[1] != nil { + arg1 = args[1].(model.PersonID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *SubjectCachedRepo_GetPersonRelated_Call) Return(_a0 []domain.SubjectPersonRelation, _a1 error) *SubjectCachedRepo_GetPersonRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *SubjectCachedRepo_GetPersonRelated_Call) Return(subjectPersonRelations []domain.SubjectPersonRelation, err error) *SubjectCachedRepo_GetPersonRelated_Call { + _c.Call.Return(subjectPersonRelations, err) return _c } -func (_c *SubjectCachedRepo_GetPersonRelated_Call) RunAndReturn(run func(context.Context, uint32) ([]domain.SubjectPersonRelation, error)) *SubjectCachedRepo_GetPersonRelated_Call { +func (_c *SubjectCachedRepo_GetPersonRelated_Call) RunAndReturn(run func(ctx context.Context, personID model.PersonID) ([]domain.SubjectPersonRelation, error)) *SubjectCachedRepo_GetPersonRelated_Call { _c.Call.Return(run) return _c } -// GetSubjectRelated provides a mock function with given fields: ctx, subjectID -func (_m *SubjectCachedRepo) GetSubjectRelated(ctx context.Context, subjectID uint32) ([]domain.SubjectInternalRelation, error) { - ret := _m.Called(ctx, subjectID) +// GetSubjectRelated provides a mock function for the type SubjectCachedRepo +func (_mock *SubjectCachedRepo) GetSubjectRelated(ctx context.Context, subjectID model.SubjectID) ([]domain.SubjectInternalRelation, error) { + ret := _mock.Called(ctx, subjectID) + + if len(ret) == 0 { + panic("no return value specified for GetSubjectRelated") + } var r0 []domain.SubjectInternalRelation var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) ([]domain.SubjectInternalRelation, error)); ok { - return rf(ctx, subjectID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID) ([]domain.SubjectInternalRelation, error)); ok { + return returnFunc(ctx, subjectID) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) []domain.SubjectInternalRelation); ok { - r0 = rf(ctx, subjectID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID) []domain.SubjectInternalRelation); ok { + r0 = returnFunc(ctx, subjectID) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]domain.SubjectInternalRelation) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, subjectID) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.SubjectID) error); ok { + r1 = returnFunc(ctx, subjectID) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -335,39 +577,35 @@ type SubjectCachedRepo_GetSubjectRelated_Call struct { // GetSubjectRelated is a helper method to define mock.On call // - ctx context.Context -// - subjectID uint32 +// - subjectID model.SubjectID func (_e *SubjectCachedRepo_Expecter) GetSubjectRelated(ctx interface{}, subjectID interface{}) *SubjectCachedRepo_GetSubjectRelated_Call { return &SubjectCachedRepo_GetSubjectRelated_Call{Call: _e.mock.On("GetSubjectRelated", ctx, subjectID)} } -func (_c *SubjectCachedRepo_GetSubjectRelated_Call) Run(run func(ctx context.Context, subjectID uint32)) *SubjectCachedRepo_GetSubjectRelated_Call { +func (_c *SubjectCachedRepo_GetSubjectRelated_Call) Run(run func(ctx context.Context, subjectID model.SubjectID)) *SubjectCachedRepo_GetSubjectRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.SubjectID + if args[1] != nil { + arg1 = args[1].(model.SubjectID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *SubjectCachedRepo_GetSubjectRelated_Call) Return(_a0 []domain.SubjectInternalRelation, _a1 error) *SubjectCachedRepo_GetSubjectRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *SubjectCachedRepo_GetSubjectRelated_Call) Return(subjectInternalRelations []domain.SubjectInternalRelation, err error) *SubjectCachedRepo_GetSubjectRelated_Call { + _c.Call.Return(subjectInternalRelations, err) return _c } -func (_c *SubjectCachedRepo_GetSubjectRelated_Call) RunAndReturn(run func(context.Context, uint32) ([]domain.SubjectInternalRelation, error)) *SubjectCachedRepo_GetSubjectRelated_Call { +func (_c *SubjectCachedRepo_GetSubjectRelated_Call) RunAndReturn(run func(ctx context.Context, subjectID model.SubjectID) ([]domain.SubjectInternalRelation, error)) *SubjectCachedRepo_GetSubjectRelated_Call { _c.Call.Return(run) return _c } - -type mockConstructorTestingTNewSubjectCachedRepo interface { - mock.TestingT - Cleanup(func()) -} - -// NewSubjectCachedRepo creates a new instance of SubjectCachedRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewSubjectCachedRepo(t mockConstructorTestingTNewSubjectCachedRepo) *SubjectCachedRepo { - mock := &SubjectCachedRepo{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/mocks/SubjectRepo.go b/internal/mocks/SubjectRepo.go index b45868d29..2da3c639f 100644 --- a/internal/mocks/SubjectRepo.go +++ b/internal/mocks/SubjectRepo.go @@ -1,17 +1,31 @@ -// Code generated by mockery v2.23.1. DO NOT EDIT. +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify package mocks import ( - context "context" + "context" - domain "github.com/bangumi/server/domain" + "github.com/bangumi/server/domain" + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/subject" mock "github.com/stretchr/testify/mock" +) - model "github.com/bangumi/server/internal/model" +// NewSubjectRepo creates a new instance of SubjectRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewSubjectRepo(t interface { + mock.TestingT + Cleanup(func()) +}) *SubjectRepo { + mock := &SubjectRepo{} + mock.Mock.Test(t) - subject "github.com/bangumi/server/internal/subject" -) + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} // SubjectRepo is an autogenerated mock type for the Repo type type SubjectRepo struct { @@ -26,27 +40,175 @@ func (_m *SubjectRepo) EXPECT() *SubjectRepo_Expecter { return &SubjectRepo_Expecter{mock: &_m.Mock} } -// Get provides a mock function with given fields: ctx, id, filter -func (_m *SubjectRepo) Get(ctx context.Context, id uint32, filter subject.Filter) (model.Subject, error) { - ret := _m.Called(ctx, id, filter) +// Browse provides a mock function for the type SubjectRepo +func (_mock *SubjectRepo) Browse(ctx context.Context, filter subject.BrowseFilter, limit int, offset int) ([]model.Subject, error) { + ret := _mock.Called(ctx, filter, limit, offset) - var r0 model.Subject + if len(ret) == 0 { + panic("no return value specified for Browse") + } + + var r0 []model.Subject var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, subject.Filter) (model.Subject, error)); ok { - return rf(ctx, id, filter) + if returnFunc, ok := ret.Get(0).(func(context.Context, subject.BrowseFilter, int, int) ([]model.Subject, error)); ok { + return returnFunc(ctx, filter, limit, offset) } - if rf, ok := ret.Get(0).(func(context.Context, uint32, subject.Filter) model.Subject); ok { - r0 = rf(ctx, id, filter) + if returnFunc, ok := ret.Get(0).(func(context.Context, subject.BrowseFilter, int, int) []model.Subject); ok { + r0 = returnFunc(ctx, filter, limit, offset) } else { - r0 = ret.Get(0).(model.Subject) + if ret.Get(0) != nil { + r0 = ret.Get(0).([]model.Subject) + } } + if returnFunc, ok := ret.Get(1).(func(context.Context, subject.BrowseFilter, int, int) error); ok { + r1 = returnFunc(ctx, filter, limit, offset) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} - if rf, ok := ret.Get(1).(func(context.Context, uint32, subject.Filter) error); ok { - r1 = rf(ctx, id, filter) +// SubjectRepo_Browse_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Browse' +type SubjectRepo_Browse_Call struct { + *mock.Call +} + +// Browse is a helper method to define mock.On call +// - ctx context.Context +// - filter subject.BrowseFilter +// - limit int +// - offset int +func (_e *SubjectRepo_Expecter) Browse(ctx interface{}, filter interface{}, limit interface{}, offset interface{}) *SubjectRepo_Browse_Call { + return &SubjectRepo_Browse_Call{Call: _e.mock.On("Browse", ctx, filter, limit, offset)} +} + +func (_c *SubjectRepo_Browse_Call) Run(run func(ctx context.Context, filter subject.BrowseFilter, limit int, offset int)) *SubjectRepo_Browse_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 subject.BrowseFilter + if args[1] != nil { + arg1 = args[1].(subject.BrowseFilter) + } + var arg2 int + if args[2] != nil { + arg2 = args[2].(int) + } + var arg3 int + if args[3] != nil { + arg3 = args[3].(int) + } + run( + arg0, + arg1, + arg2, + arg3, + ) + }) + return _c +} + +func (_c *SubjectRepo_Browse_Call) Return(subjects []model.Subject, err error) *SubjectRepo_Browse_Call { + _c.Call.Return(subjects, err) + return _c +} + +func (_c *SubjectRepo_Browse_Call) RunAndReturn(run func(ctx context.Context, filter subject.BrowseFilter, limit int, offset int) ([]model.Subject, error)) *SubjectRepo_Browse_Call { + _c.Call.Return(run) + return _c +} + +// Count provides a mock function for the type SubjectRepo +func (_mock *SubjectRepo) Count(ctx context.Context, filter subject.BrowseFilter) (int64, error) { + ret := _mock.Called(ctx, filter) + + if len(ret) == 0 { + panic("no return value specified for Count") + } + + var r0 int64 + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, subject.BrowseFilter) (int64, error)); ok { + return returnFunc(ctx, filter) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, subject.BrowseFilter) int64); ok { + r0 = returnFunc(ctx, filter) + } else { + r0 = ret.Get(0).(int64) + } + if returnFunc, ok := ret.Get(1).(func(context.Context, subject.BrowseFilter) error); ok { + r1 = returnFunc(ctx, filter) } else { r1 = ret.Error(1) } + return r0, r1 +} + +// SubjectRepo_Count_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Count' +type SubjectRepo_Count_Call struct { + *mock.Call +} + +// Count is a helper method to define mock.On call +// - ctx context.Context +// - filter subject.BrowseFilter +func (_e *SubjectRepo_Expecter) Count(ctx interface{}, filter interface{}) *SubjectRepo_Count_Call { + return &SubjectRepo_Count_Call{Call: _e.mock.On("Count", ctx, filter)} +} +func (_c *SubjectRepo_Count_Call) Run(run func(ctx context.Context, filter subject.BrowseFilter)) *SubjectRepo_Count_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 subject.BrowseFilter + if args[1] != nil { + arg1 = args[1].(subject.BrowseFilter) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *SubjectRepo_Count_Call) Return(n int64, err error) *SubjectRepo_Count_Call { + _c.Call.Return(n, err) + return _c +} + +func (_c *SubjectRepo_Count_Call) RunAndReturn(run func(ctx context.Context, filter subject.BrowseFilter) (int64, error)) *SubjectRepo_Count_Call { + _c.Call.Return(run) + return _c +} + +// Get provides a mock function for the type SubjectRepo +func (_mock *SubjectRepo) Get(ctx context.Context, id model.SubjectID, filter subject.Filter) (model.Subject, error) { + ret := _mock.Called(ctx, id, filter) + + if len(ret) == 0 { + panic("no return value specified for Get") + } + + var r0 model.Subject + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID, subject.Filter) (model.Subject, error)); ok { + return returnFunc(ctx, id, filter) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID, subject.Filter) model.Subject); ok { + r0 = returnFunc(ctx, id, filter) + } else { + r0 = ret.Get(0).(model.Subject) + } + if returnFunc, ok := ret.Get(1).(func(context.Context, model.SubjectID, subject.Filter) error); ok { + r1 = returnFunc(ctx, id, filter) + } else { + r1 = ret.Error(1) + } return r0, r1 } @@ -57,52 +219,70 @@ type SubjectRepo_Get_Call struct { // Get is a helper method to define mock.On call // - ctx context.Context -// - id uint32 +// - id model.SubjectID // - filter subject.Filter func (_e *SubjectRepo_Expecter) Get(ctx interface{}, id interface{}, filter interface{}) *SubjectRepo_Get_Call { return &SubjectRepo_Get_Call{Call: _e.mock.On("Get", ctx, id, filter)} } -func (_c *SubjectRepo_Get_Call) Run(run func(ctx context.Context, id uint32, filter subject.Filter)) *SubjectRepo_Get_Call { +func (_c *SubjectRepo_Get_Call) Run(run func(ctx context.Context, id model.SubjectID, filter subject.Filter)) *SubjectRepo_Get_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(subject.Filter)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.SubjectID + if args[1] != nil { + arg1 = args[1].(model.SubjectID) + } + var arg2 subject.Filter + if args[2] != nil { + arg2 = args[2].(subject.Filter) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } -func (_c *SubjectRepo_Get_Call) Return(_a0 model.Subject, _a1 error) *SubjectRepo_Get_Call { - _c.Call.Return(_a0, _a1) +func (_c *SubjectRepo_Get_Call) Return(subject1 model.Subject, err error) *SubjectRepo_Get_Call { + _c.Call.Return(subject1, err) return _c } -func (_c *SubjectRepo_Get_Call) RunAndReturn(run func(context.Context, uint32, subject.Filter) (model.Subject, error)) *SubjectRepo_Get_Call { +func (_c *SubjectRepo_Get_Call) RunAndReturn(run func(ctx context.Context, id model.SubjectID, filter subject.Filter) (model.Subject, error)) *SubjectRepo_Get_Call { _c.Call.Return(run) return _c } -// GetActors provides a mock function with given fields: ctx, subjectID, characterIDs -func (_m *SubjectRepo) GetActors(ctx context.Context, subjectID uint32, characterIDs []uint32) (map[uint32][]uint32, error) { - ret := _m.Called(ctx, subjectID, characterIDs) +// GetActors provides a mock function for the type SubjectRepo +func (_mock *SubjectRepo) GetActors(ctx context.Context, subjectID model.SubjectID, characterIDs []model.CharacterID) (map[model.CharacterID][]model.PersonID, error) { + ret := _mock.Called(ctx, subjectID, characterIDs) - var r0 map[uint32][]uint32 + if len(ret) == 0 { + panic("no return value specified for GetActors") + } + + var r0 map[model.CharacterID][]model.PersonID var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, []uint32) (map[uint32][]uint32, error)); ok { - return rf(ctx, subjectID, characterIDs) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID, []model.CharacterID) (map[model.CharacterID][]model.PersonID, error)); ok { + return returnFunc(ctx, subjectID, characterIDs) } - if rf, ok := ret.Get(0).(func(context.Context, uint32, []uint32) map[uint32][]uint32); ok { - r0 = rf(ctx, subjectID, characterIDs) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID, []model.CharacterID) map[model.CharacterID][]model.PersonID); ok { + r0 = returnFunc(ctx, subjectID, characterIDs) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(map[uint32][]uint32) + r0 = ret.Get(0).(map[model.CharacterID][]model.PersonID) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, []uint32) error); ok { - r1 = rf(ctx, subjectID, characterIDs) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.SubjectID, []model.CharacterID) error); ok { + r1 = returnFunc(ctx, subjectID, characterIDs) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -113,52 +293,70 @@ type SubjectRepo_GetActors_Call struct { // GetActors is a helper method to define mock.On call // - ctx context.Context -// - subjectID uint32 -// - characterIDs []uint32 +// - subjectID model.SubjectID +// - characterIDs []model.CharacterID func (_e *SubjectRepo_Expecter) GetActors(ctx interface{}, subjectID interface{}, characterIDs interface{}) *SubjectRepo_GetActors_Call { return &SubjectRepo_GetActors_Call{Call: _e.mock.On("GetActors", ctx, subjectID, characterIDs)} } -func (_c *SubjectRepo_GetActors_Call) Run(run func(ctx context.Context, subjectID uint32, characterIDs []uint32)) *SubjectRepo_GetActors_Call { +func (_c *SubjectRepo_GetActors_Call) Run(run func(ctx context.Context, subjectID model.SubjectID, characterIDs []model.CharacterID)) *SubjectRepo_GetActors_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].([]uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.SubjectID + if args[1] != nil { + arg1 = args[1].(model.SubjectID) + } + var arg2 []model.CharacterID + if args[2] != nil { + arg2 = args[2].([]model.CharacterID) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } -func (_c *SubjectRepo_GetActors_Call) Return(_a0 map[uint32][]uint32, _a1 error) *SubjectRepo_GetActors_Call { - _c.Call.Return(_a0, _a1) +func (_c *SubjectRepo_GetActors_Call) Return(vToVs map[model.CharacterID][]model.PersonID, err error) *SubjectRepo_GetActors_Call { + _c.Call.Return(vToVs, err) return _c } -func (_c *SubjectRepo_GetActors_Call) RunAndReturn(run func(context.Context, uint32, []uint32) (map[uint32][]uint32, error)) *SubjectRepo_GetActors_Call { +func (_c *SubjectRepo_GetActors_Call) RunAndReturn(run func(ctx context.Context, subjectID model.SubjectID, characterIDs []model.CharacterID) (map[model.CharacterID][]model.PersonID, error)) *SubjectRepo_GetActors_Call { _c.Call.Return(run) return _c } -// GetByIDs provides a mock function with given fields: ctx, ids, filter -func (_m *SubjectRepo) GetByIDs(ctx context.Context, ids []uint32, filter subject.Filter) (map[uint32]model.Subject, error) { - ret := _m.Called(ctx, ids, filter) +// GetByIDs provides a mock function for the type SubjectRepo +func (_mock *SubjectRepo) GetByIDs(ctx context.Context, ids []model.SubjectID, filter subject.Filter) (map[model.SubjectID]model.Subject, error) { + ret := _mock.Called(ctx, ids, filter) + + if len(ret) == 0 { + panic("no return value specified for GetByIDs") + } - var r0 map[uint32]model.Subject + var r0 map[model.SubjectID]model.Subject var r1 error - if rf, ok := ret.Get(0).(func(context.Context, []uint32, subject.Filter) (map[uint32]model.Subject, error)); ok { - return rf(ctx, ids, filter) + if returnFunc, ok := ret.Get(0).(func(context.Context, []model.SubjectID, subject.Filter) (map[model.SubjectID]model.Subject, error)); ok { + return returnFunc(ctx, ids, filter) } - if rf, ok := ret.Get(0).(func(context.Context, []uint32, subject.Filter) map[uint32]model.Subject); ok { - r0 = rf(ctx, ids, filter) + if returnFunc, ok := ret.Get(0).(func(context.Context, []model.SubjectID, subject.Filter) map[model.SubjectID]model.Subject); ok { + r0 = returnFunc(ctx, ids, filter) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(map[uint32]model.Subject) + r0 = ret.Get(0).(map[model.SubjectID]model.Subject) } } - - if rf, ok := ret.Get(1).(func(context.Context, []uint32, subject.Filter) error); ok { - r1 = rf(ctx, ids, filter) + if returnFunc, ok := ret.Get(1).(func(context.Context, []model.SubjectID, subject.Filter) error); ok { + r1 = returnFunc(ctx, ids, filter) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -169,52 +367,70 @@ type SubjectRepo_GetByIDs_Call struct { // GetByIDs is a helper method to define mock.On call // - ctx context.Context -// - ids []uint32 +// - ids []model.SubjectID // - filter subject.Filter func (_e *SubjectRepo_Expecter) GetByIDs(ctx interface{}, ids interface{}, filter interface{}) *SubjectRepo_GetByIDs_Call { return &SubjectRepo_GetByIDs_Call{Call: _e.mock.On("GetByIDs", ctx, ids, filter)} } -func (_c *SubjectRepo_GetByIDs_Call) Run(run func(ctx context.Context, ids []uint32, filter subject.Filter)) *SubjectRepo_GetByIDs_Call { +func (_c *SubjectRepo_GetByIDs_Call) Run(run func(ctx context.Context, ids []model.SubjectID, filter subject.Filter)) *SubjectRepo_GetByIDs_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].([]uint32), args[2].(subject.Filter)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []model.SubjectID + if args[1] != nil { + arg1 = args[1].([]model.SubjectID) + } + var arg2 subject.Filter + if args[2] != nil { + arg2 = args[2].(subject.Filter) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } -func (_c *SubjectRepo_GetByIDs_Call) Return(_a0 map[uint32]model.Subject, _a1 error) *SubjectRepo_GetByIDs_Call { - _c.Call.Return(_a0, _a1) +func (_c *SubjectRepo_GetByIDs_Call) Return(vToSubject map[model.SubjectID]model.Subject, err error) *SubjectRepo_GetByIDs_Call { + _c.Call.Return(vToSubject, err) return _c } -func (_c *SubjectRepo_GetByIDs_Call) RunAndReturn(run func(context.Context, []uint32, subject.Filter) (map[uint32]model.Subject, error)) *SubjectRepo_GetByIDs_Call { +func (_c *SubjectRepo_GetByIDs_Call) RunAndReturn(run func(ctx context.Context, ids []model.SubjectID, filter subject.Filter) (map[model.SubjectID]model.Subject, error)) *SubjectRepo_GetByIDs_Call { _c.Call.Return(run) return _c } -// GetCharacterRelated provides a mock function with given fields: ctx, characterID -func (_m *SubjectRepo) GetCharacterRelated(ctx context.Context, characterID uint32) ([]domain.SubjectCharacterRelation, error) { - ret := _m.Called(ctx, characterID) +// GetCharacterRelated provides a mock function for the type SubjectRepo +func (_mock *SubjectRepo) GetCharacterRelated(ctx context.Context, characterID model.CharacterID) ([]domain.SubjectCharacterRelation, error) { + ret := _mock.Called(ctx, characterID) + + if len(ret) == 0 { + panic("no return value specified for GetCharacterRelated") + } var r0 []domain.SubjectCharacterRelation var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) ([]domain.SubjectCharacterRelation, error)); ok { - return rf(ctx, characterID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.CharacterID) ([]domain.SubjectCharacterRelation, error)); ok { + return returnFunc(ctx, characterID) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) []domain.SubjectCharacterRelation); ok { - r0 = rf(ctx, characterID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.CharacterID) []domain.SubjectCharacterRelation); ok { + r0 = returnFunc(ctx, characterID) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]domain.SubjectCharacterRelation) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, characterID) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.CharacterID) error); ok { + r1 = returnFunc(ctx, characterID) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -225,51 +441,64 @@ type SubjectRepo_GetCharacterRelated_Call struct { // GetCharacterRelated is a helper method to define mock.On call // - ctx context.Context -// - characterID uint32 +// - characterID model.CharacterID func (_e *SubjectRepo_Expecter) GetCharacterRelated(ctx interface{}, characterID interface{}) *SubjectRepo_GetCharacterRelated_Call { return &SubjectRepo_GetCharacterRelated_Call{Call: _e.mock.On("GetCharacterRelated", ctx, characterID)} } -func (_c *SubjectRepo_GetCharacterRelated_Call) Run(run func(ctx context.Context, characterID uint32)) *SubjectRepo_GetCharacterRelated_Call { +func (_c *SubjectRepo_GetCharacterRelated_Call) Run(run func(ctx context.Context, characterID model.CharacterID)) *SubjectRepo_GetCharacterRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.CharacterID + if args[1] != nil { + arg1 = args[1].(model.CharacterID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *SubjectRepo_GetCharacterRelated_Call) Return(_a0 []domain.SubjectCharacterRelation, _a1 error) *SubjectRepo_GetCharacterRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *SubjectRepo_GetCharacterRelated_Call) Return(subjectCharacterRelations []domain.SubjectCharacterRelation, err error) *SubjectRepo_GetCharacterRelated_Call { + _c.Call.Return(subjectCharacterRelations, err) return _c } -func (_c *SubjectRepo_GetCharacterRelated_Call) RunAndReturn(run func(context.Context, uint32) ([]domain.SubjectCharacterRelation, error)) *SubjectRepo_GetCharacterRelated_Call { +func (_c *SubjectRepo_GetCharacterRelated_Call) RunAndReturn(run func(ctx context.Context, characterID model.CharacterID) ([]domain.SubjectCharacterRelation, error)) *SubjectRepo_GetCharacterRelated_Call { _c.Call.Return(run) return _c } -// GetPersonRelated provides a mock function with given fields: ctx, personID -func (_m *SubjectRepo) GetPersonRelated(ctx context.Context, personID uint32) ([]domain.SubjectPersonRelation, error) { - ret := _m.Called(ctx, personID) +// GetPersonRelated provides a mock function for the type SubjectRepo +func (_mock *SubjectRepo) GetPersonRelated(ctx context.Context, personID model.PersonID) ([]domain.SubjectPersonRelation, error) { + ret := _mock.Called(ctx, personID) + + if len(ret) == 0 { + panic("no return value specified for GetPersonRelated") + } var r0 []domain.SubjectPersonRelation var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) ([]domain.SubjectPersonRelation, error)); ok { - return rf(ctx, personID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.PersonID) ([]domain.SubjectPersonRelation, error)); ok { + return returnFunc(ctx, personID) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) []domain.SubjectPersonRelation); ok { - r0 = rf(ctx, personID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.PersonID) []domain.SubjectPersonRelation); ok { + r0 = returnFunc(ctx, personID) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]domain.SubjectPersonRelation) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, personID) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.PersonID) error); ok { + r1 = returnFunc(ctx, personID) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -280,51 +509,64 @@ type SubjectRepo_GetPersonRelated_Call struct { // GetPersonRelated is a helper method to define mock.On call // - ctx context.Context -// - personID uint32 +// - personID model.PersonID func (_e *SubjectRepo_Expecter) GetPersonRelated(ctx interface{}, personID interface{}) *SubjectRepo_GetPersonRelated_Call { return &SubjectRepo_GetPersonRelated_Call{Call: _e.mock.On("GetPersonRelated", ctx, personID)} } -func (_c *SubjectRepo_GetPersonRelated_Call) Run(run func(ctx context.Context, personID uint32)) *SubjectRepo_GetPersonRelated_Call { +func (_c *SubjectRepo_GetPersonRelated_Call) Run(run func(ctx context.Context, personID model.PersonID)) *SubjectRepo_GetPersonRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.PersonID + if args[1] != nil { + arg1 = args[1].(model.PersonID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *SubjectRepo_GetPersonRelated_Call) Return(_a0 []domain.SubjectPersonRelation, _a1 error) *SubjectRepo_GetPersonRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *SubjectRepo_GetPersonRelated_Call) Return(subjectPersonRelations []domain.SubjectPersonRelation, err error) *SubjectRepo_GetPersonRelated_Call { + _c.Call.Return(subjectPersonRelations, err) return _c } -func (_c *SubjectRepo_GetPersonRelated_Call) RunAndReturn(run func(context.Context, uint32) ([]domain.SubjectPersonRelation, error)) *SubjectRepo_GetPersonRelated_Call { +func (_c *SubjectRepo_GetPersonRelated_Call) RunAndReturn(run func(ctx context.Context, personID model.PersonID) ([]domain.SubjectPersonRelation, error)) *SubjectRepo_GetPersonRelated_Call { _c.Call.Return(run) return _c } -// GetSubjectRelated provides a mock function with given fields: ctx, subjectID -func (_m *SubjectRepo) GetSubjectRelated(ctx context.Context, subjectID uint32) ([]domain.SubjectInternalRelation, error) { - ret := _m.Called(ctx, subjectID) +// GetSubjectRelated provides a mock function for the type SubjectRepo +func (_mock *SubjectRepo) GetSubjectRelated(ctx context.Context, subjectID model.SubjectID) ([]domain.SubjectInternalRelation, error) { + ret := _mock.Called(ctx, subjectID) + + if len(ret) == 0 { + panic("no return value specified for GetSubjectRelated") + } var r0 []domain.SubjectInternalRelation var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) ([]domain.SubjectInternalRelation, error)); ok { - return rf(ctx, subjectID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID) ([]domain.SubjectInternalRelation, error)); ok { + return returnFunc(ctx, subjectID) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) []domain.SubjectInternalRelation); ok { - r0 = rf(ctx, subjectID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID) []domain.SubjectInternalRelation); ok { + r0 = returnFunc(ctx, subjectID) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]domain.SubjectInternalRelation) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, subjectID) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.SubjectID) error); ok { + r1 = returnFunc(ctx, subjectID) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -335,39 +577,35 @@ type SubjectRepo_GetSubjectRelated_Call struct { // GetSubjectRelated is a helper method to define mock.On call // - ctx context.Context -// - subjectID uint32 +// - subjectID model.SubjectID func (_e *SubjectRepo_Expecter) GetSubjectRelated(ctx interface{}, subjectID interface{}) *SubjectRepo_GetSubjectRelated_Call { return &SubjectRepo_GetSubjectRelated_Call{Call: _e.mock.On("GetSubjectRelated", ctx, subjectID)} } -func (_c *SubjectRepo_GetSubjectRelated_Call) Run(run func(ctx context.Context, subjectID uint32)) *SubjectRepo_GetSubjectRelated_Call { +func (_c *SubjectRepo_GetSubjectRelated_Call) Run(run func(ctx context.Context, subjectID model.SubjectID)) *SubjectRepo_GetSubjectRelated_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.SubjectID + if args[1] != nil { + arg1 = args[1].(model.SubjectID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *SubjectRepo_GetSubjectRelated_Call) Return(_a0 []domain.SubjectInternalRelation, _a1 error) *SubjectRepo_GetSubjectRelated_Call { - _c.Call.Return(_a0, _a1) +func (_c *SubjectRepo_GetSubjectRelated_Call) Return(subjectInternalRelations []domain.SubjectInternalRelation, err error) *SubjectRepo_GetSubjectRelated_Call { + _c.Call.Return(subjectInternalRelations, err) return _c } -func (_c *SubjectRepo_GetSubjectRelated_Call) RunAndReturn(run func(context.Context, uint32) ([]domain.SubjectInternalRelation, error)) *SubjectRepo_GetSubjectRelated_Call { +func (_c *SubjectRepo_GetSubjectRelated_Call) RunAndReturn(run func(ctx context.Context, subjectID model.SubjectID) ([]domain.SubjectInternalRelation, error)) *SubjectRepo_GetSubjectRelated_Call { _c.Call.Return(run) return _c } - -type mockConstructorTestingTNewSubjectRepo interface { - mock.TestingT - Cleanup(func()) -} - -// NewSubjectRepo creates a new instance of SubjectRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewSubjectRepo(t mockConstructorTestingTNewSubjectRepo) *SubjectRepo { - mock := &SubjectRepo{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/mocks/TagRepo.go b/internal/mocks/TagRepo.go new file mode 100644 index 000000000..8ccb89c28 --- /dev/null +++ b/internal/mocks/TagRepo.go @@ -0,0 +1,182 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package mocks + +import ( + "context" + + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/tag" + mock "github.com/stretchr/testify/mock" +) + +// NewTagRepo creates a new instance of TagRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewTagRepo(t interface { + mock.TestingT + Cleanup(func()) +}) *TagRepo { + mock := &TagRepo{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// TagRepo is an autogenerated mock type for the Repo type +type TagRepo struct { + mock.Mock +} + +type TagRepo_Expecter struct { + mock *mock.Mock +} + +func (_m *TagRepo) EXPECT() *TagRepo_Expecter { + return &TagRepo_Expecter{mock: &_m.Mock} +} + +// Get provides a mock function for the type TagRepo +func (_mock *TagRepo) Get(ctx context.Context, id model.SubjectID, typeID model.SubjectType) ([]tag.Tag, error) { + ret := _mock.Called(ctx, id, typeID) + + if len(ret) == 0 { + panic("no return value specified for Get") + } + + var r0 []tag.Tag + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID, model.SubjectType) ([]tag.Tag, error)); ok { + return returnFunc(ctx, id, typeID) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, model.SubjectID, model.SubjectType) []tag.Tag); ok { + r0 = returnFunc(ctx, id, typeID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]tag.Tag) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, model.SubjectID, model.SubjectType) error); ok { + r1 = returnFunc(ctx, id, typeID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// TagRepo_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' +type TagRepo_Get_Call struct { + *mock.Call +} + +// Get is a helper method to define mock.On call +// - ctx context.Context +// - id model.SubjectID +// - typeID model.SubjectType +func (_e *TagRepo_Expecter) Get(ctx interface{}, id interface{}, typeID interface{}) *TagRepo_Get_Call { + return &TagRepo_Get_Call{Call: _e.mock.On("Get", ctx, id, typeID)} +} + +func (_c *TagRepo_Get_Call) Run(run func(ctx context.Context, id model.SubjectID, typeID model.SubjectType)) *TagRepo_Get_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.SubjectID + if args[1] != nil { + arg1 = args[1].(model.SubjectID) + } + var arg2 model.SubjectType + if args[2] != nil { + arg2 = args[2].(model.SubjectType) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *TagRepo_Get_Call) Return(tags []tag.Tag, err error) *TagRepo_Get_Call { + _c.Call.Return(tags, err) + return _c +} + +func (_c *TagRepo_Get_Call) RunAndReturn(run func(ctx context.Context, id model.SubjectID, typeID model.SubjectType) ([]tag.Tag, error)) *TagRepo_Get_Call { + _c.Call.Return(run) + return _c +} + +// GetByIDs provides a mock function for the type TagRepo +func (_mock *TagRepo) GetByIDs(ctx context.Context, ids []model.SubjectID) (map[model.SubjectID][]tag.Tag, error) { + ret := _mock.Called(ctx, ids) + + if len(ret) == 0 { + panic("no return value specified for GetByIDs") + } + + var r0 map[model.SubjectID][]tag.Tag + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, []model.SubjectID) (map[model.SubjectID][]tag.Tag, error)); ok { + return returnFunc(ctx, ids) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, []model.SubjectID) map[model.SubjectID][]tag.Tag); ok { + r0 = returnFunc(ctx, ids) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[model.SubjectID][]tag.Tag) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, []model.SubjectID) error); ok { + r1 = returnFunc(ctx, ids) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// TagRepo_GetByIDs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByIDs' +type TagRepo_GetByIDs_Call struct { + *mock.Call +} + +// GetByIDs is a helper method to define mock.On call +// - ctx context.Context +// - ids []model.SubjectID +func (_e *TagRepo_Expecter) GetByIDs(ctx interface{}, ids interface{}) *TagRepo_GetByIDs_Call { + return &TagRepo_GetByIDs_Call{Call: _e.mock.On("GetByIDs", ctx, ids)} +} + +func (_c *TagRepo_GetByIDs_Call) Run(run func(ctx context.Context, ids []model.SubjectID)) *TagRepo_GetByIDs_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []model.SubjectID + if args[1] != nil { + arg1 = args[1].([]model.SubjectID) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *TagRepo_GetByIDs_Call) Return(vToTags map[model.SubjectID][]tag.Tag, err error) *TagRepo_GetByIDs_Call { + _c.Call.Return(vToTags, err) + return _c +} + +func (_c *TagRepo_GetByIDs_Call) RunAndReturn(run func(ctx context.Context, ids []model.SubjectID) (map[model.SubjectID][]tag.Tag, error)) *TagRepo_GetByIDs_Call { + _c.Call.Return(run) + return _c +} diff --git a/internal/mocks/TimeLineService.go b/internal/mocks/TimeLineService.go deleted file mode 100644 index 27594da9a..000000000 --- a/internal/mocks/TimeLineService.go +++ /dev/null @@ -1,182 +0,0 @@ -// Code generated by mockery v2.23.1. DO NOT EDIT. - -package mocks - -import ( - context "context" - - auth "github.com/bangumi/server/internal/auth" - collection "github.com/bangumi/server/internal/collections/domain/collection" - - episode "github.com/bangumi/server/internal/episode" - - mock "github.com/stretchr/testify/mock" - - model "github.com/bangumi/server/internal/model" -) - -// TimeLineService is an autogenerated mock type for the Service type -type TimeLineService struct { - mock.Mock -} - -type TimeLineService_Expecter struct { - mock *mock.Mock -} - -func (_m *TimeLineService) EXPECT() *TimeLineService_Expecter { - return &TimeLineService_Expecter{mock: &_m.Mock} -} - -// ChangeEpisodeStatus provides a mock function with given fields: ctx, u, sbj, _a3 -func (_m *TimeLineService) ChangeEpisodeStatus(ctx context.Context, u auth.Auth, sbj model.Subject, _a3 episode.Episode) error { - ret := _m.Called(ctx, u, sbj, _a3) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, auth.Auth, model.Subject, episode.Episode) error); ok { - r0 = rf(ctx, u, sbj, _a3) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// TimeLineService_ChangeEpisodeStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ChangeEpisodeStatus' -type TimeLineService_ChangeEpisodeStatus_Call struct { - *mock.Call -} - -// ChangeEpisodeStatus is a helper method to define mock.On call -// - ctx context.Context -// - u auth.Auth -// - sbj model.Subject -// - _a3 episode.Episode -func (_e *TimeLineService_Expecter) ChangeEpisodeStatus(ctx interface{}, u interface{}, sbj interface{}, _a3 interface{}) *TimeLineService_ChangeEpisodeStatus_Call { - return &TimeLineService_ChangeEpisodeStatus_Call{Call: _e.mock.On("ChangeEpisodeStatus", ctx, u, sbj, _a3)} -} - -func (_c *TimeLineService_ChangeEpisodeStatus_Call) Run(run func(ctx context.Context, u auth.Auth, sbj model.Subject, _a3 episode.Episode)) *TimeLineService_ChangeEpisodeStatus_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(auth.Auth), args[2].(model.Subject), args[3].(episode.Episode)) - }) - return _c -} - -func (_c *TimeLineService_ChangeEpisodeStatus_Call) Return(_a0 error) *TimeLineService_ChangeEpisodeStatus_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *TimeLineService_ChangeEpisodeStatus_Call) RunAndReturn(run func(context.Context, auth.Auth, model.Subject, episode.Episode) error) *TimeLineService_ChangeEpisodeStatus_Call { - _c.Call.Return(run) - return _c -} - -// ChangeSubjectCollection provides a mock function with given fields: ctx, u, sbj, collect, comment, rate -func (_m *TimeLineService) ChangeSubjectCollection(ctx context.Context, u uint32, sbj model.Subject, collect collection.SubjectCollection, comment string, rate uint8) error { - ret := _m.Called(ctx, u, sbj, collect, comment, rate) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, model.Subject, collection.SubjectCollection, string, uint8) error); ok { - r0 = rf(ctx, u, sbj, collect, comment, rate) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// TimeLineService_ChangeSubjectCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ChangeSubjectCollection' -type TimeLineService_ChangeSubjectCollection_Call struct { - *mock.Call -} - -// ChangeSubjectCollection is a helper method to define mock.On call -// - ctx context.Context -// - u uint32 -// - sbj model.Subject -// - collect collection.SubjectCollection -// - comment string -// - rate uint8 -func (_e *TimeLineService_Expecter) ChangeSubjectCollection(ctx interface{}, u interface{}, sbj interface{}, collect interface{}, comment interface{}, rate interface{}) *TimeLineService_ChangeSubjectCollection_Call { - return &TimeLineService_ChangeSubjectCollection_Call{Call: _e.mock.On("ChangeSubjectCollection", ctx, u, sbj, collect, comment, rate)} -} - -func (_c *TimeLineService_ChangeSubjectCollection_Call) Run(run func(ctx context.Context, u uint32, sbj model.Subject, collect collection.SubjectCollection, comment string, rate uint8)) *TimeLineService_ChangeSubjectCollection_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(model.Subject), args[3].(collection.SubjectCollection), args[4].(string), args[5].(uint8)) - }) - return _c -} - -func (_c *TimeLineService_ChangeSubjectCollection_Call) Return(_a0 error) *TimeLineService_ChangeSubjectCollection_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *TimeLineService_ChangeSubjectCollection_Call) RunAndReturn(run func(context.Context, uint32, model.Subject, collection.SubjectCollection, string, uint8) error) *TimeLineService_ChangeSubjectCollection_Call { - _c.Call.Return(run) - return _c -} - -// ChangeSubjectProgress provides a mock function with given fields: ctx, u, sbj, epsUpdate, volsUpdate -func (_m *TimeLineService) ChangeSubjectProgress(ctx context.Context, u uint32, sbj model.Subject, epsUpdate uint32, volsUpdate uint32) error { - ret := _m.Called(ctx, u, sbj, epsUpdate, volsUpdate) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, model.Subject, uint32, uint32) error); ok { - r0 = rf(ctx, u, sbj, epsUpdate, volsUpdate) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// TimeLineService_ChangeSubjectProgress_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ChangeSubjectProgress' -type TimeLineService_ChangeSubjectProgress_Call struct { - *mock.Call -} - -// ChangeSubjectProgress is a helper method to define mock.On call -// - ctx context.Context -// - u uint32 -// - sbj model.Subject -// - epsUpdate uint32 -// - volsUpdate uint32 -func (_e *TimeLineService_Expecter) ChangeSubjectProgress(ctx interface{}, u interface{}, sbj interface{}, epsUpdate interface{}, volsUpdate interface{}) *TimeLineService_ChangeSubjectProgress_Call { - return &TimeLineService_ChangeSubjectProgress_Call{Call: _e.mock.On("ChangeSubjectProgress", ctx, u, sbj, epsUpdate, volsUpdate)} -} - -func (_c *TimeLineService_ChangeSubjectProgress_Call) Run(run func(ctx context.Context, u uint32, sbj model.Subject, epsUpdate uint32, volsUpdate uint32)) *TimeLineService_ChangeSubjectProgress_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(model.Subject), args[3].(uint32), args[4].(uint32)) - }) - return _c -} - -func (_c *TimeLineService_ChangeSubjectProgress_Call) Return(_a0 error) *TimeLineService_ChangeSubjectProgress_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *TimeLineService_ChangeSubjectProgress_Call) RunAndReturn(run func(context.Context, uint32, model.Subject, uint32, uint32) error) *TimeLineService_ChangeSubjectProgress_Call { - _c.Call.Return(run) - return _c -} - -type mockConstructorTestingTNewTimeLineService interface { - mock.TestingT - Cleanup(func()) -} - -// NewTimeLineService creates a new instance of TimeLineService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewTimeLineService(t mockConstructorTestingTNewTimeLineService) *TimeLineService { - mock := &TimeLineService{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/mocks/TimelineService.go b/internal/mocks/TimelineService.go new file mode 100644 index 000000000..103dc8908 --- /dev/null +++ b/internal/mocks/TimelineService.go @@ -0,0 +1,279 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package mocks + +import ( + "context" + + "github.com/bangumi/server/internal/auth" + "github.com/bangumi/server/internal/collections/domain/collection" + "github.com/bangumi/server/internal/episode" + "github.com/bangumi/server/internal/model" + mock "github.com/stretchr/testify/mock" +) + +// NewTimelineService creates a new instance of TimelineService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewTimelineService(t interface { + mock.TestingT + Cleanup(func()) +}) *TimelineService { + mock := &TimelineService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// TimelineService is an autogenerated mock type for the Service type +type TimelineService struct { + mock.Mock +} + +type TimelineService_Expecter struct { + mock *mock.Mock +} + +func (_m *TimelineService) EXPECT() *TimelineService_Expecter { + return &TimelineService_Expecter{mock: &_m.Mock} +} + +// ChangeEpisodeStatus provides a mock function for the type TimelineService +func (_mock *TimelineService) ChangeEpisodeStatus(ctx context.Context, u auth.Auth, sbj model.Subject, episode1 episode.Episode, t collection.EpisodeCollection) error { + ret := _mock.Called(ctx, u, sbj, episode1, t) + + if len(ret) == 0 { + panic("no return value specified for ChangeEpisodeStatus") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, auth.Auth, model.Subject, episode.Episode, collection.EpisodeCollection) error); ok { + r0 = returnFunc(ctx, u, sbj, episode1, t) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// TimelineService_ChangeEpisodeStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ChangeEpisodeStatus' +type TimelineService_ChangeEpisodeStatus_Call struct { + *mock.Call +} + +// ChangeEpisodeStatus is a helper method to define mock.On call +// - ctx context.Context +// - u auth.Auth +// - sbj model.Subject +// - episode1 episode.Episode +// - t collection.EpisodeCollection +func (_e *TimelineService_Expecter) ChangeEpisodeStatus(ctx interface{}, u interface{}, sbj interface{}, episode1 interface{}, t interface{}) *TimelineService_ChangeEpisodeStatus_Call { + return &TimelineService_ChangeEpisodeStatus_Call{Call: _e.mock.On("ChangeEpisodeStatus", ctx, u, sbj, episode1, t)} +} + +func (_c *TimelineService_ChangeEpisodeStatus_Call) Run(run func(ctx context.Context, u auth.Auth, sbj model.Subject, episode1 episode.Episode, t collection.EpisodeCollection)) *TimelineService_ChangeEpisodeStatus_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 auth.Auth + if args[1] != nil { + arg1 = args[1].(auth.Auth) + } + var arg2 model.Subject + if args[2] != nil { + arg2 = args[2].(model.Subject) + } + var arg3 episode.Episode + if args[3] != nil { + arg3 = args[3].(episode.Episode) + } + var arg4 collection.EpisodeCollection + if args[4] != nil { + arg4 = args[4].(collection.EpisodeCollection) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + ) + }) + return _c +} + +func (_c *TimelineService_ChangeEpisodeStatus_Call) Return(err error) *TimelineService_ChangeEpisodeStatus_Call { + _c.Call.Return(err) + return _c +} + +func (_c *TimelineService_ChangeEpisodeStatus_Call) RunAndReturn(run func(ctx context.Context, u auth.Auth, sbj model.Subject, episode1 episode.Episode, t collection.EpisodeCollection) error) *TimelineService_ChangeEpisodeStatus_Call { + _c.Call.Return(run) + return _c +} + +// ChangeSubjectCollection provides a mock function for the type TimelineService +func (_mock *TimelineService) ChangeSubjectCollection(ctx context.Context, u model.UserID, sbj model.Subject, collect collection.SubjectCollection, collectID uint64, comment string, rate uint8) error { + ret := _mock.Called(ctx, u, sbj, collect, collectID, comment, rate) + + if len(ret) == 0 { + panic("no return value specified for ChangeSubjectCollection") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, model.Subject, collection.SubjectCollection, uint64, string, uint8) error); ok { + r0 = returnFunc(ctx, u, sbj, collect, collectID, comment, rate) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// TimelineService_ChangeSubjectCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ChangeSubjectCollection' +type TimelineService_ChangeSubjectCollection_Call struct { + *mock.Call +} + +// ChangeSubjectCollection is a helper method to define mock.On call +// - ctx context.Context +// - u model.UserID +// - sbj model.Subject +// - collect collection.SubjectCollection +// - collectID uint64 +// - comment string +// - rate uint8 +func (_e *TimelineService_Expecter) ChangeSubjectCollection(ctx interface{}, u interface{}, sbj interface{}, collect interface{}, collectID interface{}, comment interface{}, rate interface{}) *TimelineService_ChangeSubjectCollection_Call { + return &TimelineService_ChangeSubjectCollection_Call{Call: _e.mock.On("ChangeSubjectCollection", ctx, u, sbj, collect, collectID, comment, rate)} +} + +func (_c *TimelineService_ChangeSubjectCollection_Call) Run(run func(ctx context.Context, u model.UserID, sbj model.Subject, collect collection.SubjectCollection, collectID uint64, comment string, rate uint8)) *TimelineService_ChangeSubjectCollection_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.UserID + if args[1] != nil { + arg1 = args[1].(model.UserID) + } + var arg2 model.Subject + if args[2] != nil { + arg2 = args[2].(model.Subject) + } + var arg3 collection.SubjectCollection + if args[3] != nil { + arg3 = args[3].(collection.SubjectCollection) + } + var arg4 uint64 + if args[4] != nil { + arg4 = args[4].(uint64) + } + var arg5 string + if args[5] != nil { + arg5 = args[5].(string) + } + var arg6 uint8 + if args[6] != nil { + arg6 = args[6].(uint8) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + arg6, + ) + }) + return _c +} + +func (_c *TimelineService_ChangeSubjectCollection_Call) Return(err error) *TimelineService_ChangeSubjectCollection_Call { + _c.Call.Return(err) + return _c +} + +func (_c *TimelineService_ChangeSubjectCollection_Call) RunAndReturn(run func(ctx context.Context, u model.UserID, sbj model.Subject, collect collection.SubjectCollection, collectID uint64, comment string, rate uint8) error) *TimelineService_ChangeSubjectCollection_Call { + _c.Call.Return(run) + return _c +} + +// ChangeSubjectProgress provides a mock function for the type TimelineService +func (_mock *TimelineService) ChangeSubjectProgress(ctx context.Context, u model.UserID, sbj model.Subject, epsUpdate uint32, volsUpdate uint32) error { + ret := _mock.Called(ctx, u, sbj, epsUpdate, volsUpdate) + + if len(ret) == 0 { + panic("no return value specified for ChangeSubjectProgress") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, model.Subject, uint32, uint32) error); ok { + r0 = returnFunc(ctx, u, sbj, epsUpdate, volsUpdate) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// TimelineService_ChangeSubjectProgress_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ChangeSubjectProgress' +type TimelineService_ChangeSubjectProgress_Call struct { + *mock.Call +} + +// ChangeSubjectProgress is a helper method to define mock.On call +// - ctx context.Context +// - u model.UserID +// - sbj model.Subject +// - epsUpdate uint32 +// - volsUpdate uint32 +func (_e *TimelineService_Expecter) ChangeSubjectProgress(ctx interface{}, u interface{}, sbj interface{}, epsUpdate interface{}, volsUpdate interface{}) *TimelineService_ChangeSubjectProgress_Call { + return &TimelineService_ChangeSubjectProgress_Call{Call: _e.mock.On("ChangeSubjectProgress", ctx, u, sbj, epsUpdate, volsUpdate)} +} + +func (_c *TimelineService_ChangeSubjectProgress_Call) Run(run func(ctx context.Context, u model.UserID, sbj model.Subject, epsUpdate uint32, volsUpdate uint32)) *TimelineService_ChangeSubjectProgress_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.UserID + if args[1] != nil { + arg1 = args[1].(model.UserID) + } + var arg2 model.Subject + if args[2] != nil { + arg2 = args[2].(model.Subject) + } + var arg3 uint32 + if args[3] != nil { + arg3 = args[3].(uint32) + } + var arg4 uint32 + if args[4] != nil { + arg4 = args[4].(uint32) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + ) + }) + return _c +} + +func (_c *TimelineService_ChangeSubjectProgress_Call) Return(err error) *TimelineService_ChangeSubjectProgress_Call { + _c.Call.Return(err) + return _c +} + +func (_c *TimelineService_ChangeSubjectProgress_Call) RunAndReturn(run func(ctx context.Context, u model.UserID, sbj model.Subject, epsUpdate uint32, volsUpdate uint32) error) *TimelineService_ChangeSubjectProgress_Call { + _c.Call.Return(run) + return _c +} diff --git a/internal/mocks/UserRepo.go b/internal/mocks/UserRepo.go index 6730cf5e7..f9f9ab9eb 100644 --- a/internal/mocks/UserRepo.go +++ b/internal/mocks/UserRepo.go @@ -1,14 +1,31 @@ -// Code generated by mockery v2.23.1. DO NOT EDIT. +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify package mocks import ( - context "context" + "context" - user "github.com/bangumi/server/internal/user" + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/user" mock "github.com/stretchr/testify/mock" ) +// NewUserRepo creates a new instance of UserRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewUserRepo(t interface { + mock.TestingT + Cleanup(func()) +}) *UserRepo { + mock := &UserRepo{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + // UserRepo is an autogenerated mock type for the Repo type type UserRepo struct { mock.Mock @@ -22,34 +39,35 @@ func (_m *UserRepo) EXPECT() *UserRepo_Expecter { return &UserRepo_Expecter{mock: &_m.Mock} } -// CheckIsFriendToOthers provides a mock function with given fields: ctx, selfID, otherIDs -func (_m *UserRepo) CheckIsFriendToOthers(ctx context.Context, selfID uint32, otherIDs ...uint32) (bool, error) { - _va := make([]interface{}, len(otherIDs)) - for _i := range otherIDs { - _va[_i] = otherIDs[_i] +// CheckIsFriendToOthers provides a mock function for the type UserRepo +func (_mock *UserRepo) CheckIsFriendToOthers(ctx context.Context, selfID model.UserID, otherIDs ...model.UserID) (bool, error) { + var tmpRet mock.Arguments + if len(otherIDs) > 0 { + tmpRet = _mock.Called(ctx, selfID, otherIDs) + } else { + tmpRet = _mock.Called(ctx, selfID) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for CheckIsFriendToOthers") } - var _ca []interface{} - _ca = append(_ca, ctx, selfID) - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) var r0 bool var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, ...uint32) (bool, error)); ok { - return rf(ctx, selfID, otherIDs...) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, ...model.UserID) (bool, error)); ok { + return returnFunc(ctx, selfID, otherIDs...) } - if rf, ok := ret.Get(0).(func(context.Context, uint32, ...uint32) bool); ok { - r0 = rf(ctx, selfID, otherIDs...) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID, ...model.UserID) bool); ok { + r0 = returnFunc(ctx, selfID, otherIDs...) } else { r0 = ret.Get(0).(bool) } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, ...uint32) error); ok { - r1 = rf(ctx, selfID, otherIDs...) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.UserID, ...model.UserID) error); ok { + r1 = returnFunc(ctx, selfID, otherIDs...) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -60,57 +78,71 @@ type UserRepo_CheckIsFriendToOthers_Call struct { // CheckIsFriendToOthers is a helper method to define mock.On call // - ctx context.Context -// - selfID uint32 -// - otherIDs ...uint32 +// - selfID model.UserID +// - otherIDs ...model.UserID func (_e *UserRepo_Expecter) CheckIsFriendToOthers(ctx interface{}, selfID interface{}, otherIDs ...interface{}) *UserRepo_CheckIsFriendToOthers_Call { return &UserRepo_CheckIsFriendToOthers_Call{Call: _e.mock.On("CheckIsFriendToOthers", append([]interface{}{ctx, selfID}, otherIDs...)...)} } -func (_c *UserRepo_CheckIsFriendToOthers_Call) Run(run func(ctx context.Context, selfID uint32, otherIDs ...uint32)) *UserRepo_CheckIsFriendToOthers_Call { +func (_c *UserRepo_CheckIsFriendToOthers_Call) Run(run func(ctx context.Context, selfID model.UserID, otherIDs ...model.UserID)) *UserRepo_CheckIsFriendToOthers_Call { _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]uint32, len(args)-2) - for i, a := range args[2:] { - if a != nil { - variadicArgs[i] = a.(uint32) - } + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) } - run(args[0].(context.Context), args[1].(uint32), variadicArgs...) + var arg1 model.UserID + if args[1] != nil { + arg1 = args[1].(model.UserID) + } + var arg2 []model.UserID + var variadicArgs []model.UserID + if len(args) > 2 { + variadicArgs = args[2].([]model.UserID) + } + arg2 = variadicArgs + run( + arg0, + arg1, + arg2..., + ) }) return _c } -func (_c *UserRepo_CheckIsFriendToOthers_Call) Return(_a0 bool, _a1 error) *UserRepo_CheckIsFriendToOthers_Call { - _c.Call.Return(_a0, _a1) +func (_c *UserRepo_CheckIsFriendToOthers_Call) Return(b bool, err error) *UserRepo_CheckIsFriendToOthers_Call { + _c.Call.Return(b, err) return _c } -func (_c *UserRepo_CheckIsFriendToOthers_Call) RunAndReturn(run func(context.Context, uint32, ...uint32) (bool, error)) *UserRepo_CheckIsFriendToOthers_Call { +func (_c *UserRepo_CheckIsFriendToOthers_Call) RunAndReturn(run func(ctx context.Context, selfID model.UserID, otherIDs ...model.UserID) (bool, error)) *UserRepo_CheckIsFriendToOthers_Call { _c.Call.Return(run) return _c } -// GetByID provides a mock function with given fields: ctx, userID -func (_m *UserRepo) GetByID(ctx context.Context, userID uint32) (user.User, error) { - ret := _m.Called(ctx, userID) +// GetByID provides a mock function for the type UserRepo +func (_mock *UserRepo) GetByID(ctx context.Context, userID model.UserID) (user.User, error) { + ret := _mock.Called(ctx, userID) + + if len(ret) == 0 { + panic("no return value specified for GetByID") + } var r0 user.User var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (user.User, error)); ok { - return rf(ctx, userID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID) (user.User, error)); ok { + return returnFunc(ctx, userID) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) user.User); ok { - r0 = rf(ctx, userID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID) user.User); ok { + r0 = returnFunc(ctx, userID) } else { r0 = ret.Get(0).(user.User) } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, userID) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.UserID) error); ok { + r1 = returnFunc(ctx, userID) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -121,51 +153,64 @@ type UserRepo_GetByID_Call struct { // GetByID is a helper method to define mock.On call // - ctx context.Context -// - userID uint32 +// - userID model.UserID func (_e *UserRepo_Expecter) GetByID(ctx interface{}, userID interface{}) *UserRepo_GetByID_Call { return &UserRepo_GetByID_Call{Call: _e.mock.On("GetByID", ctx, userID)} } -func (_c *UserRepo_GetByID_Call) Run(run func(ctx context.Context, userID uint32)) *UserRepo_GetByID_Call { +func (_c *UserRepo_GetByID_Call) Run(run func(ctx context.Context, userID model.UserID)) *UserRepo_GetByID_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.UserID + if args[1] != nil { + arg1 = args[1].(model.UserID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *UserRepo_GetByID_Call) Return(_a0 user.User, _a1 error) *UserRepo_GetByID_Call { - _c.Call.Return(_a0, _a1) +func (_c *UserRepo_GetByID_Call) Return(user1 user.User, err error) *UserRepo_GetByID_Call { + _c.Call.Return(user1, err) return _c } -func (_c *UserRepo_GetByID_Call) RunAndReturn(run func(context.Context, uint32) (user.User, error)) *UserRepo_GetByID_Call { +func (_c *UserRepo_GetByID_Call) RunAndReturn(run func(ctx context.Context, userID model.UserID) (user.User, error)) *UserRepo_GetByID_Call { _c.Call.Return(run) return _c } -// GetByIDs provides a mock function with given fields: ctx, ids -func (_m *UserRepo) GetByIDs(ctx context.Context, ids []uint32) (map[uint32]user.User, error) { - ret := _m.Called(ctx, ids) +// GetByIDs provides a mock function for the type UserRepo +func (_mock *UserRepo) GetByIDs(ctx context.Context, ids []model.UserID) (map[model.UserID]user.User, error) { + ret := _mock.Called(ctx, ids) + + if len(ret) == 0 { + panic("no return value specified for GetByIDs") + } - var r0 map[uint32]user.User + var r0 map[model.UserID]user.User var r1 error - if rf, ok := ret.Get(0).(func(context.Context, []uint32) (map[uint32]user.User, error)); ok { - return rf(ctx, ids) + if returnFunc, ok := ret.Get(0).(func(context.Context, []model.UserID) (map[model.UserID]user.User, error)); ok { + return returnFunc(ctx, ids) } - if rf, ok := ret.Get(0).(func(context.Context, []uint32) map[uint32]user.User); ok { - r0 = rf(ctx, ids) + if returnFunc, ok := ret.Get(0).(func(context.Context, []model.UserID) map[model.UserID]user.User); ok { + r0 = returnFunc(ctx, ids) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(map[uint32]user.User) + r0 = ret.Get(0).(map[model.UserID]user.User) } } - - if rf, ok := ret.Get(1).(func(context.Context, []uint32) error); ok { - r1 = rf(ctx, ids) + if returnFunc, ok := ret.Get(1).(func(context.Context, []model.UserID) error); ok { + r1 = returnFunc(ctx, ids) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -176,49 +221,62 @@ type UserRepo_GetByIDs_Call struct { // GetByIDs is a helper method to define mock.On call // - ctx context.Context -// - ids []uint32 +// - ids []model.UserID func (_e *UserRepo_Expecter) GetByIDs(ctx interface{}, ids interface{}) *UserRepo_GetByIDs_Call { return &UserRepo_GetByIDs_Call{Call: _e.mock.On("GetByIDs", ctx, ids)} } -func (_c *UserRepo_GetByIDs_Call) Run(run func(ctx context.Context, ids []uint32)) *UserRepo_GetByIDs_Call { +func (_c *UserRepo_GetByIDs_Call) Run(run func(ctx context.Context, ids []model.UserID)) *UserRepo_GetByIDs_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].([]uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []model.UserID + if args[1] != nil { + arg1 = args[1].([]model.UserID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *UserRepo_GetByIDs_Call) Return(_a0 map[uint32]user.User, _a1 error) *UserRepo_GetByIDs_Call { - _c.Call.Return(_a0, _a1) +func (_c *UserRepo_GetByIDs_Call) Return(vToUser map[model.UserID]user.User, err error) *UserRepo_GetByIDs_Call { + _c.Call.Return(vToUser, err) return _c } -func (_c *UserRepo_GetByIDs_Call) RunAndReturn(run func(context.Context, []uint32) (map[uint32]user.User, error)) *UserRepo_GetByIDs_Call { +func (_c *UserRepo_GetByIDs_Call) RunAndReturn(run func(ctx context.Context, ids []model.UserID) (map[model.UserID]user.User, error)) *UserRepo_GetByIDs_Call { _c.Call.Return(run) return _c } -// GetByName provides a mock function with given fields: ctx, username -func (_m *UserRepo) GetByName(ctx context.Context, username string) (user.User, error) { - ret := _m.Called(ctx, username) +// GetByName provides a mock function for the type UserRepo +func (_mock *UserRepo) GetByName(ctx context.Context, username string) (user.User, error) { + ret := _mock.Called(ctx, username) + + if len(ret) == 0 { + panic("no return value specified for GetByName") + } var r0 user.User var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (user.User, error)); ok { - return rf(ctx, username) + if returnFunc, ok := ret.Get(0).(func(context.Context, string) (user.User, error)); ok { + return returnFunc(ctx, username) } - if rf, ok := ret.Get(0).(func(context.Context, string) user.User); ok { - r0 = rf(ctx, username) + if returnFunc, ok := ret.Get(0).(func(context.Context, string) user.User); ok { + r0 = returnFunc(ctx, username) } else { r0 = ret.Get(0).(user.User) } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, username) + if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = returnFunc(ctx, username) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -236,44 +294,57 @@ func (_e *UserRepo_Expecter) GetByName(ctx interface{}, username interface{}) *U func (_c *UserRepo_GetByName_Call) Run(run func(ctx context.Context, username string)) *UserRepo_GetByName_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *UserRepo_GetByName_Call) Return(_a0 user.User, _a1 error) *UserRepo_GetByName_Call { - _c.Call.Return(_a0, _a1) +func (_c *UserRepo_GetByName_Call) Return(user1 user.User, err error) *UserRepo_GetByName_Call { + _c.Call.Return(user1, err) return _c } -func (_c *UserRepo_GetByName_Call) RunAndReturn(run func(context.Context, string) (user.User, error)) *UserRepo_GetByName_Call { +func (_c *UserRepo_GetByName_Call) RunAndReturn(run func(ctx context.Context, username string) (user.User, error)) *UserRepo_GetByName_Call { _c.Call.Return(run) return _c } -// GetFieldsByIDs provides a mock function with given fields: ctx, ids -func (_m *UserRepo) GetFieldsByIDs(ctx context.Context, ids []uint32) (map[uint32]user.Fields, error) { - ret := _m.Called(ctx, ids) +// GetFieldsByIDs provides a mock function for the type UserRepo +func (_mock *UserRepo) GetFieldsByIDs(ctx context.Context, ids []model.UserID) (map[model.UserID]user.Fields, error) { + ret := _mock.Called(ctx, ids) - var r0 map[uint32]user.Fields + if len(ret) == 0 { + panic("no return value specified for GetFieldsByIDs") + } + + var r0 map[model.UserID]user.Fields var r1 error - if rf, ok := ret.Get(0).(func(context.Context, []uint32) (map[uint32]user.Fields, error)); ok { - return rf(ctx, ids) + if returnFunc, ok := ret.Get(0).(func(context.Context, []model.UserID) (map[model.UserID]user.Fields, error)); ok { + return returnFunc(ctx, ids) } - if rf, ok := ret.Get(0).(func(context.Context, []uint32) map[uint32]user.Fields); ok { - r0 = rf(ctx, ids) + if returnFunc, ok := ret.Get(0).(func(context.Context, []model.UserID) map[model.UserID]user.Fields); ok { + r0 = returnFunc(ctx, ids) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(map[uint32]user.Fields) + r0 = ret.Get(0).(map[model.UserID]user.Fields) } } - - if rf, ok := ret.Get(1).(func(context.Context, []uint32) error); ok { - r1 = rf(ctx, ids) + if returnFunc, ok := ret.Get(1).(func(context.Context, []model.UserID) error); ok { + r1 = returnFunc(ctx, ids) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -284,51 +355,64 @@ type UserRepo_GetFieldsByIDs_Call struct { // GetFieldsByIDs is a helper method to define mock.On call // - ctx context.Context -// - ids []uint32 +// - ids []model.UserID func (_e *UserRepo_Expecter) GetFieldsByIDs(ctx interface{}, ids interface{}) *UserRepo_GetFieldsByIDs_Call { return &UserRepo_GetFieldsByIDs_Call{Call: _e.mock.On("GetFieldsByIDs", ctx, ids)} } -func (_c *UserRepo_GetFieldsByIDs_Call) Run(run func(ctx context.Context, ids []uint32)) *UserRepo_GetFieldsByIDs_Call { +func (_c *UserRepo_GetFieldsByIDs_Call) Run(run func(ctx context.Context, ids []model.UserID)) *UserRepo_GetFieldsByIDs_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].([]uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []model.UserID + if args[1] != nil { + arg1 = args[1].([]model.UserID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *UserRepo_GetFieldsByIDs_Call) Return(_a0 map[uint32]user.Fields, _a1 error) *UserRepo_GetFieldsByIDs_Call { - _c.Call.Return(_a0, _a1) +func (_c *UserRepo_GetFieldsByIDs_Call) Return(vToFields map[model.UserID]user.Fields, err error) *UserRepo_GetFieldsByIDs_Call { + _c.Call.Return(vToFields, err) return _c } -func (_c *UserRepo_GetFieldsByIDs_Call) RunAndReturn(run func(context.Context, []uint32) (map[uint32]user.Fields, error)) *UserRepo_GetFieldsByIDs_Call { +func (_c *UserRepo_GetFieldsByIDs_Call) RunAndReturn(run func(ctx context.Context, ids []model.UserID) (map[model.UserID]user.Fields, error)) *UserRepo_GetFieldsByIDs_Call { _c.Call.Return(run) return _c } -// GetFriends provides a mock function with given fields: ctx, userID -func (_m *UserRepo) GetFriends(ctx context.Context, userID uint32) (map[uint32]user.FriendItem, error) { - ret := _m.Called(ctx, userID) +// GetFriends provides a mock function for the type UserRepo +func (_mock *UserRepo) GetFriends(ctx context.Context, userID model.UserID) (map[model.UserID]user.FriendItem, error) { + ret := _mock.Called(ctx, userID) + + if len(ret) == 0 { + panic("no return value specified for GetFriends") + } - var r0 map[uint32]user.FriendItem + var r0 map[model.UserID]user.FriendItem var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (map[uint32]user.FriendItem, error)); ok { - return rf(ctx, userID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID) (map[model.UserID]user.FriendItem, error)); ok { + return returnFunc(ctx, userID) } - if rf, ok := ret.Get(0).(func(context.Context, uint32) map[uint32]user.FriendItem); ok { - r0 = rf(ctx, userID) + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID) map[model.UserID]user.FriendItem); ok { + r0 = returnFunc(ctx, userID) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(map[uint32]user.FriendItem) + r0 = ret.Get(0).(map[model.UserID]user.FriendItem) } } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, userID) + if returnFunc, ok := ret.Get(1).(func(context.Context, model.UserID) error); ok { + r1 = returnFunc(ctx, userID) } else { r1 = ret.Error(1) } - return r0, r1 } @@ -339,39 +423,101 @@ type UserRepo_GetFriends_Call struct { // GetFriends is a helper method to define mock.On call // - ctx context.Context -// - userID uint32 +// - userID model.UserID func (_e *UserRepo_Expecter) GetFriends(ctx interface{}, userID interface{}) *UserRepo_GetFriends_Call { return &UserRepo_GetFriends_Call{Call: _e.mock.On("GetFriends", ctx, userID)} } -func (_c *UserRepo_GetFriends_Call) Run(run func(ctx context.Context, userID uint32)) *UserRepo_GetFriends_Call { +func (_c *UserRepo_GetFriends_Call) Run(run func(ctx context.Context, userID model.UserID)) *UserRepo_GetFriends_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.UserID + if args[1] != nil { + arg1 = args[1].(model.UserID) + } + run( + arg0, + arg1, + ) }) return _c } -func (_c *UserRepo_GetFriends_Call) Return(_a0 map[uint32]user.FriendItem, _a1 error) *UserRepo_GetFriends_Call { - _c.Call.Return(_a0, _a1) +func (_c *UserRepo_GetFriends_Call) Return(vToFriendItem map[model.UserID]user.FriendItem, err error) *UserRepo_GetFriends_Call { + _c.Call.Return(vToFriendItem, err) return _c } -func (_c *UserRepo_GetFriends_Call) RunAndReturn(run func(context.Context, uint32) (map[uint32]user.FriendItem, error)) *UserRepo_GetFriends_Call { +func (_c *UserRepo_GetFriends_Call) RunAndReturn(run func(ctx context.Context, userID model.UserID) (map[model.UserID]user.FriendItem, error)) *UserRepo_GetFriends_Call { _c.Call.Return(run) return _c } -type mockConstructorTestingTNewUserRepo interface { - mock.TestingT - Cleanup(func()) +// GetFullUser provides a mock function for the type UserRepo +func (_mock *UserRepo) GetFullUser(ctx context.Context, userID model.UserID) (user.FullUser, error) { + ret := _mock.Called(ctx, userID) + + if len(ret) == 0 { + panic("no return value specified for GetFullUser") + } + + var r0 user.FullUser + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID) (user.FullUser, error)); ok { + return returnFunc(ctx, userID) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, model.UserID) user.FullUser); ok { + r0 = returnFunc(ctx, userID) + } else { + r0 = ret.Get(0).(user.FullUser) + } + if returnFunc, ok := ret.Get(1).(func(context.Context, model.UserID) error); ok { + r1 = returnFunc(ctx, userID) + } else { + r1 = ret.Error(1) + } + return r0, r1 } -// NewUserRepo creates a new instance of UserRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewUserRepo(t mockConstructorTestingTNewUserRepo) *UserRepo { - mock := &UserRepo{} - mock.Mock.Test(t) +// UserRepo_GetFullUser_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFullUser' +type UserRepo_GetFullUser_Call struct { + *mock.Call +} - t.Cleanup(func() { mock.AssertExpectations(t) }) +// GetFullUser is a helper method to define mock.On call +// - ctx context.Context +// - userID model.UserID +func (_e *UserRepo_Expecter) GetFullUser(ctx interface{}, userID interface{}) *UserRepo_GetFullUser_Call { + return &UserRepo_GetFullUser_Call{Call: _e.mock.On("GetFullUser", ctx, userID)} +} - return mock +func (_c *UserRepo_GetFullUser_Call) Run(run func(ctx context.Context, userID model.UserID)) *UserRepo_GetFullUser_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 model.UserID + if args[1] != nil { + arg1 = args[1].(model.UserID) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *UserRepo_GetFullUser_Call) Return(fullUser user.FullUser, err error) *UserRepo_GetFullUser_Call { + _c.Call.Return(fullUser, err) + return _c +} + +func (_c *UserRepo_GetFullUser_Call) RunAndReturn(run func(ctx context.Context, userID model.UserID) (user.FullUser, error)) *UserRepo_GetFullUser_Call { + _c.Call.Return(run) + return _c } diff --git a/internal/model/index.go b/internal/model/index.go index 7642c2f65..707eeda8e 100644 --- a/internal/model/index.go +++ b/internal/model/index.go @@ -16,6 +16,14 @@ package model import "time" +type IndexPrivacy uint8 + +const ( + IndexPrivacyPublic IndexPrivacy = 0 + IndexPrivacyDeleted IndexPrivacy = 1 + IndexPrivacyPrivate IndexPrivacy = 2 +) + type Index struct { CreatedAt time.Time UpdatedAt time.Time @@ -26,6 +34,6 @@ type Index struct { ID IndexID Comments uint32 Collects uint32 - Ban bool NSFW bool + Privacy IndexPrivacy } diff --git a/internal/model/person.go b/internal/model/person.go index e1431f690..9d8abc02c 100644 --- a/internal/model/person.go +++ b/internal/model/person.go @@ -62,10 +62,6 @@ func (p Person) Careers() []string { s = append(s, "seiyu") } - if p.Writer { - s = append(s, "writer") - } - if p.Illustrator { s = append(s, "illustrator") } diff --git a/internal/model/relation.go b/internal/model/relation.go index 63589d551..b997cda9a 100644 --- a/internal/model/relation.go +++ b/internal/model/relation.go @@ -24,6 +24,7 @@ type SubjectPersonRelation struct { Person Person Subject Subject TypeID uint16 + Eps string } type SubjectCharacterRelation struct { diff --git a/internal/model/subject.go b/internal/model/subject.go index 577c9e55f..256dc8e02 100644 --- a/internal/model/subject.go +++ b/internal/model/subject.go @@ -17,14 +17,16 @@ package model const subjectLocked = 2 type Tag struct { - Name string - Count int + Name string + Count uint + TotalCount uint } type Subject struct { Image string Summary string Name string + MetaTags string Date string // first release date NameCN string Infobox string diff --git a/internal/subject/repo2.go b/internal/model/tag.go similarity index 85% rename from internal/subject/repo2.go rename to internal/model/tag.go index 32f36ee71..becc2f209 100644 --- a/internal/subject/repo2.go +++ b/internal/model/tag.go @@ -12,4 +12,10 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see -package subject +package model + +// TagCatSubject 条目tag. +const TagCatSubject = 0 + +// TagCatMeta 官方tag. +const TagCatMeta = 3 diff --git a/internal/notification/model.go b/internal/notification/model.go deleted file mode 100644 index 71dfcfa2a..000000000 --- a/internal/notification/model.go +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package notification - -import ( - "time" - - "github.com/bangumi/server/internal/model" -) - -type Field struct { - Title string - ID model.NotificationFieldID - RelatedID uint32 // 关联的主体事项的id,条目讨论,日志的 - RelatedType uint8 // 关联的主体事项的分类 -} - -type Notification struct { - CreatedTime time.Time - ID model.NotificationID - ReceiverID model.UserID - SenderID model.UserID - FieldID model.NotificationFieldID - RelatedID uint32 // 触发通知的实际事项id,如回复的 - Type Type - Status Status -} - -type Type uint8 - -const ( - TypeGroupTopicReply Type = 1 // 发起的小组话题有新回复 - TypeReplyToGroupTopicReply Type = 2 // 在小组话题收到回复 - TypeSubjectTopicReply Type = 3 // 发起的条目讨论有新回复 - TypeReplyToSubjectTopicReply Type = 4 // 在条目讨论收到回复 - TypeCharacterMessage Type = 5 // 关注的角色讨论有新回复 - TypeReplyToCharacterMessage Type = 6 // 在角色讨论收到回复 - TypeBlogMessage Type = 7 // 日志留言 - TypeReplyToBlogMessage Type = 8 // 日志留言的回复 - TypeEpisodeTopicReply Type = 9 // 章节讨论有新回复 - TypeReplyToEpisodeTopic Type = 10 // 在章节讨论收到回复 - TypeIndexMessage Type = 11 // 目录有新留言 - TypeReplyToIndexMessage Type = 12 // 目录留言收到回复 - TypeReplyToPersonMessage Type = 13 // 人物留言收到回复 - TypeFriendRequest Type = 14 // 收到好友申请 - TypePassFriendRequest Type = 15 // 好友申请通过 - TypeDoujinClubTopicReply Type = 17 // 同人社团讨论有新回复 - TypeReplyToDoujinClubTopicReply Type = 18 // 在同人社团讨论收到回复 - TypeReplyToDoujinSubjectTopicReply Type = 19 // 同人作品讨论有新回复 - TypeDoujinEventTopicReply Type = 20 // 同人展会讨论有新回复 - TypeReplyToDoujinEventTopicReply Type = 21 // 在同人展会讨论收到回复 - TypeTsukkomiReply Type = 22 // 吐槽有新回复 - TypeGroupTopicMention Type = 23 // 在小组讨论中被提及 - TypeSubjectTopicMention Type = 24 // 在条目讨论中被提及 - TypeCharacterMessageMention Type = 25 // 在角色留言中被提及 - TypePersonMessageMention Type = 26 // 在人物留言中被提及 - TypeIndexMessageMention Type = 27 // 在目录留言中被提及 - TypeTsukkomiMention Type = 28 // 在吐槽中被提及 - TypeBlogMessageMention Type = 29 // 在日志留言中被提及 - TypeEpisodeTopicMention Type = 30 // 在章节讨论中被提及 - TypeDoujinClubMessageMention Type = 31 // 在同人社团留言中被提及 - TypeDoujinClubTopicMention Type = 32 // 在同人社团讨论中被提及 - TypeDoujinSubjectMessageMention Type = 33 // 在同人作品留言中被提及 - TypeDoujinEventTopicMention Type = 34 // 在同人展会讨论中被提及 -) - -type Status uint8 - -const ( - StatusRead Status = 0 - StatusUnread Status = 1 -) diff --git a/internal/notification/mysql_repository.go b/internal/notification/mysql_repository.go deleted file mode 100644 index d326a8a11..000000000 --- a/internal/notification/mysql_repository.go +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package notification - -import ( - "context" - - "github.com/trim21/errgo" - "go.uber.org/zap" - - "github.com/bangumi/server/dal/query" - "github.com/bangumi/server/internal/model" -) - -type mysqlRepo struct { - q *query.Query - log *zap.Logger -} - -func NewMysqlRepo(q *query.Query, log *zap.Logger) (Repo, error) { - return mysqlRepo{q: q, log: log.Named("notification.mysqlRepo")}, nil -} - -func (r mysqlRepo) count(ctx context.Context, userID model.UserID) (int64, error) { //nolint:golint,unused - count, err := r.q.Notification.WithContext(ctx).Where( - r.q.Notification.ReceiverID.Eq(userID), - r.q.Notification.Status.Eq(uint8(StatusUnread)), - ).Count() - if err != nil { - r.log.Error("unexpected error", zap.Error(err)) - return 0, errgo.Wrap(err, "dal") - } - return count, nil -} - -func (r mysqlRepo) Count(ctx context.Context, userID model.UserID) (int64, error) { - member, err := r.q.Member.WithContext(ctx).Where( - r.q.Member.ID.Eq(userID), - ).Select(r.q.Member.ID, r.q.Member.NewNotify).Take() - if err != nil { - r.log.Error("unexpected error", zap.Error(err)) - return 0, errgo.Wrap(err, "dal") - } - return int64(member.NewNotify), nil -} diff --git a/internal/person/mysql_repository.go b/internal/person/mysql_repository.go index d14a8284e..2a888fd05 100644 --- a/internal/person/mysql_repository.go +++ b/internal/person/mysql_repository.go @@ -45,8 +45,6 @@ func (r mysqlRepo) Get(ctx context.Context, id model.PersonID) (model.Person, er return model.Person{}, gerr.ErrNotFound } - r.log.Error("unexpected error happened", zap.Error(err)) - return model.Person{}, errgo.Wrap(err, "dal") } @@ -59,7 +57,6 @@ func (r mysqlRepo) GetSubjectRelated( relations, err := r.q.PersonSubjects.WithContext(ctx). Where(r.q.PersonSubjects.SubjectID.Eq(subjectID)).Find() if err != nil { - r.log.Error("unexpected error happened", zap.Error(err)) return nil, errgo.Wrap(err, "dal") } @@ -68,6 +65,7 @@ func (r mysqlRepo) GetSubjectRelated( rel[i] = domain.SubjectPersonRelation{ PersonID: relation.PersonID, TypeID: relation.PrsnPosition, + Eps: relation.PrsnAppearEps, } } @@ -83,7 +81,6 @@ func (r mysqlRepo) GetCharacterRelated( Order(r.q.Cast.PersonID). Find() if err != nil { - r.log.Error("unexpected error happened", zap.Error(err)) return nil, errgo.Wrap(err, "dal") } @@ -103,7 +100,6 @@ func (r mysqlRepo) GetByIDs(ctx context.Context, ids []model.PersonID) (map[mode u, err := r.q.Person.WithContext(ctx).Joins(r.q.Person.Fields). Where(r.q.Person.ID.In(ids...)).Find() if err != nil { - r.log.Error("unexpected error happened", zap.Error(err)) return nil, errgo.Wrap(err, "dal") } @@ -120,9 +116,9 @@ func convertDao(p *dao.Person) model.Person { Redirect: p.Redirect, Type: p.Type, ID: p.ID, - Name: p.Name, + Name: string(p.Name), Image: p.Img, - Infobox: p.Infobox, + Infobox: string(p.Infobox), Summary: p.Summary, Locked: p.Ban != 0, CollectCount: p.Collects, diff --git a/internal/person/mysql_repository_internal_test.go b/internal/person/mysql_repository_internal_test.go new file mode 100644 index 000000000..578e8410d --- /dev/null +++ b/internal/person/mysql_repository_internal_test.go @@ -0,0 +1,35 @@ +package person + +import ( + "html" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/bangumi/server/dal/dao" + "github.com/bangumi/server/dal/utiltype" +) + +func TestConvertDao_UnescapesHTMLStrings(t *testing.T) { + t.Parallel() + + wikiValue := `[nickname|"First Hassan"] Tom & Jerry 'quote'` + nameValue := `Tom & Jerry ` + + person := convertDao(&dao.Person{ + Name: mustScanHTMLString(t, html.EscapeString(nameValue)), + Infobox: mustScanHTMLString(t, html.EscapeString(wikiValue)), + }) + + require.Equal(t, nameValue, person.Name) + require.Equal(t, wikiValue, person.Infobox) +} + +func mustScanHTMLString(t *testing.T, value string) utiltype.HTMLEscapedString { + t.Helper() + + var result utiltype.HTMLEscapedString + require.NoError(t, (&result).Scan(value)) + + return result +} diff --git a/internal/person/service.go b/internal/person/service.go index 7c7b5ead2..83d2948d9 100644 --- a/internal/person/service.go +++ b/internal/person/service.go @@ -65,6 +65,7 @@ func (s service) GetSubjectRelated( Person: persons[rel.PersonID], Subject: sub, TypeID: rel.TypeID, + Eps: rel.Eps, } } diff --git a/internal/pkg/cache/noop.go b/internal/pkg/cache/noop.go index 491f46aab..0555f9eaf 100644 --- a/internal/pkg/cache/noop.go +++ b/internal/pkg/cache/noop.go @@ -36,3 +36,7 @@ func (n noop) Set(context.Context, string, any, time.Duration) error { func (n noop) Del(context.Context, ...string) error { return nil } + +func (n noop) MGet(ctx context.Context, key []string, result any) error { + return nil +} diff --git a/internal/pkg/cache/redis.go b/internal/pkg/cache/redis.go index 16746dd5f..6bc235ac7 100644 --- a/internal/pkg/cache/redis.go +++ b/internal/pkg/cache/redis.go @@ -16,9 +16,10 @@ package cache import ( "context" + "reflect" "time" - "github.com/redis/go-redis/v9" + "github.com/redis/rueidis" "github.com/trim21/errgo" "go.uber.org/zap" @@ -34,31 +35,41 @@ type RedisCache interface { Get(ctx context.Context, key string, value any) (bool, error) Set(ctx context.Context, key string, value any, ttl time.Duration) error Del(ctx context.Context, keys ...string) error + + // MGet result should be `*[]T` + // for example + // + // var s []struct{} + // cache.MGet(ctx, keys, &s) + MGet(ctx context.Context, key []string, result any) error } // NewRedisCache create a redis backed cache. -func NewRedisCache(cli *redis.Client) RedisCache { - return redisCache{r: cli} +func NewRedisCache(ru rueidis.Client) RedisCache { + return redisCache{ru: ru} } type redisCache struct { - r *redis.Client + ru rueidis.Client } func (c redisCache) Get(ctx context.Context, key string, value any) (bool, error) { - raw, err := c.r.Get(ctx, key).Bytes() - if err != nil { - if err == redis.Nil { - return false, nil - } - + result := c.ru.Do(ctx, c.ru.B().Get().Key(key).Build()) + if err := result.NonRedisError(); err != nil { return false, errgo.Wrap(err, "redis get") } + raw, err := result.AsBytes() + // redis.Nil + if err != nil { + return false, nil + } + err = unmarshalBytes(raw, value) if err != nil { logger.Warn("can't unmarshal redis cached data as json", zap.String("key", key)) - c.r.Del(ctx, key) + + c.ru.Do(ctx, c.ru.B().Del().Key(key).Build()) return false, nil } @@ -66,13 +77,57 @@ func (c redisCache) Get(ctx context.Context, key string, value any) (bool, error return true, nil } -func (c redisCache) Set(ctx context.Context, key string, value any, ttl time.Duration) error { - b, err := marshalBytes(value) +// MGet result should be `*[]T` +// for example +// +// var s []struct{} +// cache.MGet(ctx, keys, &s) +func (c redisCache) MGet(ctx context.Context, keys []string, result any) error { + results, err := c.ru.Do(ctx, c.ru.B().Mget().Key(keys...).Build()).ToArray() if err != nil { - return err + //nolint:errorlint + if err == rueidis.Nil { + return nil + } + + return errgo.Wrap(err, "redis mget") + } + + // no more ideal way to do this + // reflect.ValueOf(*[]T) + rv := reflect.ValueOf(result) + if rv.Kind() != reflect.Ptr || rv.Elem().Kind() != reflect.Slice { + panic("only allow *[]T as input") + } + + // reflect.ValueOf([]T) + vv := rv.Elem() + + vv.Set(reflect.MakeSlice(rv.Elem().Type(), 0, len(results))) + elementType := rv.Elem().Type().Elem() + for _, message := range results { + var v = reflect.New(elementType) + + //nolint:errorlint + if message.Error() == rueidis.Nil { + continue + } + + e := message.DecodeJSON(v.Interface()) + if e != nil { + logger.Warn("unexpected failure when decoding json", zap.Error(e)) + continue + } + + reflect.Append(vv, v.Elem()) } - if err := c.r.Set(ctx, key, b, ttl).Err(); err != nil { + return nil +} + +func (c redisCache) Set(ctx context.Context, key string, value any, ttl time.Duration) error { + err := c.ru.Do(ctx, c.ru.B().Set().Key(key).Value(rueidis.JSON(value)).Ex(ttl).Build()).Error() + if err != nil { return errgo.Wrap(err, "redis set") } @@ -80,6 +135,6 @@ func (c redisCache) Set(ctx context.Context, key string, value any, ttl time.Dur } func (c redisCache) Del(ctx context.Context, keys ...string) error { - err := c.r.Del(ctx, keys...).Err() + err := c.ru.Do(ctx, c.ru.B().Del().Key(keys...).Build()).Error() return errgo.Wrap(err, "redis.Del") } diff --git a/internal/pkg/cache/redis_test.go b/internal/pkg/cache/redis_test.go index 6d0f89a06..897432311 100644 --- a/internal/pkg/cache/redis_test.go +++ b/internal/pkg/cache/redis_test.go @@ -16,11 +16,10 @@ package cache_test import ( "context" - "encoding/json" + "fmt" "testing" "time" - redismock "github.com/go-redis/redismock/v9" "github.com/stretchr/testify/require" "github.com/bangumi/server/internal/pkg/cache" @@ -32,106 +31,15 @@ type RedisCacheTestItem struct { I int } -func mockedCache() (cache.RedisCache, redismock.ClientMock) { - db, mock := redismock.NewClientMock() - c := cache.NewRedisCache(db) - - return c, mock -} - -func TestRedisCache_Set(t *testing.T) { - t.Parallel() - var key = t.Name() + "redis_key" - c, mock := mockedCache() - mock.Regexp().ExpectSet(key, `.*`, time.Hour).SetVal("OK") - - value := RedisCacheTestItem{ - S: "sss", - I: 2, - } - - require.NoError(t, c.Set(context.TODO(), key, value, time.Hour)) - - if err := mock.ExpectationsWereMet(); err != nil { - t.Error(err) - } -} - -func TestRedisCache_Get_Nil(t *testing.T) { - t.Parallel() - - var key = t.Name() + "redis_key" - c, mock := mockedCache() - mock.Regexp().ExpectGet(key).RedisNil() - - var result RedisCacheTestItem - - ok, err := c.Get(context.TODO(), key, &result) - require.NoError(t, err) - require.False(t, ok) - - if err := mock.ExpectationsWereMet(); err != nil { - t.Error(err) - } -} - -func TestRedisCache_Get_Cached(t *testing.T) { - t.Parallel() - - var key = t.Name() + "redis_key" - value := RedisCacheTestItem{ - S: "sss", - I: 2, - } - - c, mock := mockedCache() - encoded, err := json.Marshal(value) - require.NoError(t, err) - - mock.Regexp().ExpectGet(key).SetVal(string(encoded)) - - var result RedisCacheTestItem - - ok, err := c.Get(context.TODO(), key, &result) - require.NoError(t, err) - require.True(t, ok) - - require.Equal(t, value, result) - - if err := mock.ExpectationsWereMet(); err != nil { - t.Error(err) - } -} - -func TestRedisCache_Get_Broken(t *testing.T) { - t.Parallel() - - var key = t.Name() + "redis_key" - c, mock := mockedCache() - - mock.Regexp().ExpectGet(key).SetVal("some random broken content") - mock.Regexp().ExpectDel(key).SetVal(1) - - var result RedisCacheTestItem - - ok, err := c.Get(context.TODO(), key, &result) - require.NoError(t, err) - require.False(t, ok) - - if err := mock.ExpectationsWereMet(); err != nil { - t.Error(err) - } -} - func TestRedisCache_Real(t *testing.T) { t.Parallel() var key = t.Name() + "redis_key" - db := test.GetRedis(t) - db.Del(context.TODO(), key) + r := test.GetRedis(t) + require.NoError(t, r.Do(context.TODO(), r.B().Del().Key(key).Build()).Error()) - c := cache.NewRedisCache(db) + c := cache.NewRedisCache(r) var data = RedisCacheTestItem{S: "ss", I: 5} require.NoError(t, c.Set(context.TODO(), key, data, time.Hour)) @@ -146,16 +54,16 @@ func TestRedisCache_Real(t *testing.T) { func TestRedisCache_Del(t *testing.T) { t.Parallel() - var key = t.Name() + "redis_test " + var key = fmt.Sprintln(t.Name(), "redis_test", time.Now()) - db := test.GetRedis(t) - require.NoError(t, db.Set(context.Background(), key, "", 0).Err()) + r := test.GetRedis(t) + require.NoError(t, r.Do(context.TODO(), r.B().Set().Key(key).Value("").Build()).Error()) - c := cache.NewRedisCache(db) + c := cache.NewRedisCache(r) require.NoError(t, c.Del(context.Background(), key)) - v, err := db.Exists(context.Background(), key).Result() + exist, err := r.Do(context.TODO(), r.B().Exists().Key(key).Build()).AsBool() require.NoError(t, err) - require.True(t, v == 0) + require.False(t, exist) } diff --git a/internal/pkg/cache/serialize.go b/internal/pkg/cache/serialize.go index 5110d84ae..6ca0cd2b9 100644 --- a/internal/pkg/cache/serialize.go +++ b/internal/pkg/cache/serialize.go @@ -20,15 +20,6 @@ import ( "github.com/trim21/errgo" ) -func marshalBytes(v any) ([]byte, error) { - b, err := json.Marshal(v) - if err != nil { - return nil, errgo.Wrap(err, "json.Marshal") - } - - return b, nil -} - func unmarshalBytes(b []byte, v any) error { err := json.Unmarshal(b, v) if err != nil { diff --git a/internal/pkg/compat/wiki.go b/internal/pkg/compat/wiki.go index 9cf17176e..c1497315f 100644 --- a/internal/pkg/compat/wiki.go +++ b/internal/pkg/compat/wiki.go @@ -15,7 +15,7 @@ package compat import ( - "github.com/bangumi/server/pkg/wiki" + wiki "github.com/bangumi/wiki-parser-go" ) func V0Wiki(s wiki.Wiki) []any { diff --git a/internal/pkg/compat/wiki_internal_test.go b/internal/pkg/compat/wiki_internal_test.go index c56279af2..b7b1316ff 100644 --- a/internal/pkg/compat/wiki_internal_test.go +++ b/internal/pkg/compat/wiki_internal_test.go @@ -17,9 +17,8 @@ package compat import ( "testing" + wiki "github.com/bangumi/wiki-parser-go" "github.com/stretchr/testify/require" - - "github.com/bangumi/server/pkg/wiki" ) func TestCompat_v0wiki(t *testing.T) { diff --git a/internal/pkg/dam/dam.go b/internal/pkg/dam/dam.go index 81eb59dd0..73b6b1722 100644 --- a/internal/pkg/dam/dam.go +++ b/internal/pkg/dam/dam.go @@ -16,7 +16,6 @@ package dam import ( "regexp" - "strings" "unicode" "github.com/trim21/errgo" @@ -34,21 +33,21 @@ func New(c config.AppConfig) (Dam, error) { var cc Dam var err error if c.NsfwWord != "" { - cc.nsfwWord, err = regexp.Compile(c.NsfwWord) + cc.nsfwWord, err = regexp.Compile("(?i)" + c.NsfwWord) if err != nil { return Dam{}, errgo.Wrap(err, "nsfw_word") } } if c.DisableWords != "" { - cc.disableWord, err = regexp.Compile(c.DisableWords) + cc.disableWord, err = regexp.Compile("(?i)" + c.DisableWords) if err != nil { return Dam{}, errgo.Wrap(err, "disable_words") } } if c.BannedDomain != "" { - cc.bannedDomain, err = regexp.Compile(c.BannedDomain) + cc.bannedDomain, err = regexp.Compile("(?i)" + c.BannedDomain) if err != nil { return Dam{}, errgo.Wrap(err, "banned_domain") } @@ -65,8 +64,6 @@ func (d Dam) NeedReview(text string) bool { return false } - text = strings.ToLower(text) - return d.disableWord.MatchString(text) } @@ -85,7 +82,7 @@ func (d Dam) CensoredWords(text string) bool { func AllPrintableChar(text string) bool { for _, c := range text { switch c { - case '\n', '\t': + case '\n', '\t', '\r': continue } @@ -96,3 +93,26 @@ func AllPrintableChar(text string) bool { return true } + +var ZeroWidthPattern = regexp.MustCompile(`[^\t\r\n\p{L}\p{M}\p{N}\p{P}\p{S}\p{Z}]`) +var ExtraSpacePattern = regexp.MustCompile("[\u3000 ]") + +func ValidateTag(t string) bool { + if len(t) == 0 { + return false + } + + if !AllPrintableChar(t) { + return false + } + + if ZeroWidthPattern.MatchString(t) { + return false + } + + if ExtraSpacePattern.MatchString(t) { + return false + } + + return true +} diff --git a/internal/pkg/dam/dam_test.go b/internal/pkg/dam/dam_test.go index f41a86d56..0d39162c4 100644 --- a/internal/pkg/dam/dam_test.go +++ b/internal/pkg/dam/dam_test.go @@ -27,6 +27,7 @@ func TestAllPrintableChar(t *testing.T) { t.Parallel() require.True(t, dam.AllPrintableChar("0123456789abcdEfg\t、\n 汉字")) + require.True(t, dam.AllPrintableChar("abc\r\nabc")) require.False(t, dam.AllPrintableChar("\u202c")) require.False(t, dam.AllPrintableChar("\u202d")) diff --git a/internal/pkg/driver/mysql.go b/internal/pkg/driver/mysql.go index 480ec3a54..b05a31421 100644 --- a/internal/pkg/driver/mysql.go +++ b/internal/pkg/driver/mysql.go @@ -31,7 +31,7 @@ import ( var setLoggerOnce = sync.Once{} -func NewMysqlConnectionPool(c config.AppConfig) (*sql.DB, error) { +func NewMysqlDriver(c config.AppConfig) (*sql.DB, error) { setLoggerOnce.Do(func() { _ = mysql.SetLogger(logger.StdAt(zap.ErrorLevel)) }) @@ -52,7 +52,7 @@ func NewMysqlConnectionPool(c config.AppConfig) (*sql.DB, error) { return nil, errgo.Wrap(err, "mysql: failed to create sql connection pool") } - ctx, cancel := context.WithTimeout(context.Background(), time.Second) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() if err = db.PingContext(ctx); err != nil { diff --git a/internal/pkg/driver/redis.go b/internal/pkg/driver/redis.go index 9eb4e3eaf..8b943d7ba 100644 --- a/internal/pkg/driver/redis.go +++ b/internal/pkg/driver/redis.go @@ -15,51 +15,30 @@ package driver import ( - "context" - "time" + "fmt" + "net/url" - "github.com/redis/go-redis/v9" - "github.com/trim21/errgo" - "go.uber.org/zap" + "github.com/redis/rueidis" "github.com/bangumi/server/config" - "github.com/bangumi/server/internal/metrics" - "github.com/bangumi/server/internal/pkg/logger" ) -const defaultRedisPoolSize = 4 - -// NewRedisClient create a redis client -// use [test.GetRedis] in tests. -func NewRedisClient(c config.AppConfig) (*redis.Client, error) { - redisOptions, err := redis.ParseURL(c.RedisURL) +func NewRueidisClient(c config.AppConfig) (rueidis.Client, error) { + u, err := url.Parse(c.RedisURL) if err != nil { - logger.Fatal("redis: failed to parse redis url", zap.String("url", c.RedisURL)) - } - - if redisOptions.PoolSize == 0 { - redisOptions.PoolSize = defaultRedisPoolSize + return nil, err } - cli := redis.NewClient(redisOptions) - - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - - if err := cli.Ping(ctx).Err(); err != nil { - return nil, errgo.Wrap(err, "redis: failed to ping") - } - - return cli, nil -} - -func NewRedisClientWithMetrics(c config.AppConfig) (*redis.Client, error) { - cli, err := NewRedisClient(c) + password, _ := u.User.Password() + cli, err := rueidis.NewClient(rueidis.ClientOption{ + InitAddress: []string{fmt.Sprintf("%s:%s", u.Hostname(), u.Port())}, + Password: password, + // 1<<2 = 4 connection for each node + PipelineMultiplex: 2, + }) if err != nil { return cli, err } - cli.AddHook(metrics.RedisHook(cli.Options().Addr)) - return cli, nil } diff --git a/internal/pkg/driver/s3.go b/internal/pkg/driver/s3.go index 8c25e65f3..84a943349 100644 --- a/internal/pkg/driver/s3.go +++ b/internal/pkg/driver/s3.go @@ -15,25 +15,24 @@ package driver import ( - "github.com/minio/minio-go/v7" - "github.com/minio/minio-go/v7/pkg/credentials" - "github.com/trim21/errgo" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/bangumi/server/config" ) -func NewS3(c config.AppConfig) (*minio.Client, error) { +func NewS3(c config.AppConfig) (*s3.Client, error) { if c.S3EntryPoint == "" { return nil, nil //nolint:nilnil } - // Initialize minio client object. - minioClient, err := minio.New(c.S3EntryPoint, &minio.Options{ - Creds: credentials.NewStaticV4(c.S3AccessKey, c.S3SecretKey, ""), + svc := s3.New(s3.Options{ + BaseEndpoint: aws.String(c.S3EntryPoint), + Region: "us-east-1", + UsePathStyle: true, + Credentials: credentials.NewStaticCredentialsProvider(c.S3AccessKey, c.S3SecretKey, ""), }) - if err != nil { - return nil, errgo.Wrap(err, "s3: failed to connect to s3") - } - return minioClient, nil + return svc, nil } diff --git a/internal/pkg/generic/math.go b/internal/pkg/generic/math.go deleted file mode 100644 index 59c00b2d7..000000000 --- a/internal/pkg/generic/math.go +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package generic - -type Number interface { - Integer | Float -} - -type Signed interface { - int8 | int16 | int32 | int64 | int -} - -type Unsigned interface { - uint8 | uint16 | uint32 | uint64 | uint -} - -type Integer interface { - Signed | Unsigned -} - -type Float interface { - float32 | float64 -} - -func Min[T Number](a, b T) T { - if a < b { - return a - } - - return b -} diff --git a/internal/pkg/generic/set/set_test.go b/internal/pkg/generic/set/set_test.go index 5c8034303..cad2a3b41 100644 --- a/internal/pkg/generic/set/set_test.go +++ b/internal/pkg/generic/set/set_test.go @@ -43,7 +43,6 @@ func TestFromSlice(t *testing.T) { {"init without values", []string{}}, } for _, tc := range testcases { - tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() s := set.FromSlice[string](tc.input) diff --git a/internal/pkg/gmap/gmap.go b/internal/pkg/gmap/gmap.go new file mode 100644 index 000000000..a2917df3d --- /dev/null +++ b/internal/pkg/gmap/gmap.go @@ -0,0 +1,10 @@ +package gmap + +func SafeAssign[K comparable, V any](m map[K]V, key K, value V) map[K]V { + if m == nil { + return map[K]V{key: value} + } + + m[key] = value + return m +} diff --git a/internal/pkg/gstr/parse.go b/internal/pkg/gstr/parse.go index 7b637447a..84287ed13 100644 --- a/internal/pkg/gstr/parse.go +++ b/internal/pkg/gstr/parse.go @@ -20,14 +20,38 @@ import ( "github.com/trim21/errgo" ) +func ParseInt8(s string) (int8, error) { + v, err := strconv.ParseInt(s, 10, 8) + + return int8(v), errgo.Wrap(err, "strconv") +} + func ParseUint8(s string) (uint8, error) { v, err := strconv.ParseUint(s, 10, 8) return uint8(v), errgo.Wrap(err, "strconv") } +func ParseUint16(s string) (uint16, error) { + v, err := strconv.ParseUint(s, 10, 16) + + return uint16(v), errgo.Wrap(err, "strconv") +} + +func ParseInt32(s string) (int32, error) { + v, err := strconv.ParseInt(s, 10, 32) + + return int32(v), errgo.Wrap(err, "strconv") +} + func ParseUint32(s string) (uint32, error) { v, err := strconv.ParseUint(s, 10, 32) return uint32(v), errgo.Wrap(err, "strconv") } + +func ParseBool(s string) (bool, error) { + v, err := strconv.ParseBool(s) + + return v, errgo.Wrap(err, "strconv") +} diff --git a/internal/pkg/logger/ctx.go b/internal/pkg/logger/ctx.go index 6fcc9694f..ca71d5442 100644 --- a/internal/pkg/logger/ctx.go +++ b/internal/pkg/logger/ctx.go @@ -23,17 +23,22 @@ import ( // https://github.com/uber-go/zap/issues/654 +// make RequestKey and unique. +type key string + //nolint:gochecknoglobals -var RequestKey = &struct{}{} +const RequestKey key = "logger.contextKey" type RequestTrace struct { IP string ReqID string + Path string } func (r *RequestTrace) MarshalLogObject(enc zapcore.ObjectEncoder) error { enc.AddString("ip", r.IP) enc.AddString("request-id", r.ReqID) + enc.AddString("path", r.Path) return nil } diff --git a/internal/pkg/random/string.go b/internal/pkg/random/string.go index fb1524822..600cb653b 100644 --- a/internal/pkg/random/string.go +++ b/internal/pkg/random/string.go @@ -28,8 +28,8 @@ var p = pool.New(func() *bufio.Reader { // we may never need to change these values. const base62Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" -const base62CharsLength = 62 // len(base62Chars) -const base62MaxByte byte = 255 - (256 % base62CharsLength) //nolint:gomnd +const base62CharsLength = byte(len(base62Chars)) +const base62MaxByte = byte(255 - (256 % len(base62Chars))) //nolint:mnd // Base62String generate a cryptographically secure base62 string in given length. // Will panic if it can't read from 'crypto/rand'. @@ -39,7 +39,7 @@ func Base62String(length int) string { b := make([]byte, length) // storage for random bytes. - r := make([]byte, length+(length/4)) //nolint:gomnd + r := make([]byte, length+(length/4)) //nolint:mnd i := 0 for { diff --git a/internal/pkg/serialize/serialize.go b/internal/pkg/serialize/serialize.go new file mode 100644 index 000000000..122c4c87a --- /dev/null +++ b/internal/pkg/serialize/serialize.go @@ -0,0 +1,23 @@ +package serialize + +import ( + "bytes" + "encoding/json" + + "github.com/trim21/go-phpserialize" +) + +func Encode(v any) ([]byte, error) { + return json.Marshal(v) +} + +func Decode(data []byte, v any) error { + if len(data) == 0 { + return nil + } + if bytes.HasPrefix(data, []byte("a:")) { + return phpserialize.Unmarshal(data, v) + } + + return json.Unmarshal(data, v) +} diff --git a/internal/pkg/test/fx.go b/internal/pkg/test/fx.go new file mode 100644 index 000000000..e0720ecf7 --- /dev/null +++ b/internal/pkg/test/fx.go @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +package test + +import ( + "encoding/json" + "testing" + + "github.com/go-resty/resty/v2" + "github.com/stretchr/testify/require" + "go.uber.org/fx" + "go.uber.org/zap" + + "github.com/bangumi/server/config" + "github.com/bangumi/server/dal" + "github.com/bangumi/server/internal/pkg/cache" + "github.com/bangumi/server/internal/pkg/driver" +) + +func Fx(tb testing.TB, target ...fx.Option) { + tb.Helper() + err := fx.New( + append(target, fx.NopLogger, + + // driver and connector + fx.Provide( + config.AppConfigReader(config.AppTypeHTTP), + driver.NewRueidisClient, // redis + driver.NewMysqlDriver, // mysql + func() *resty.Client { + httpClient := resty.New().SetJSONEscapeHTML(false) + httpClient.JSONUnmarshal = json.Unmarshal + httpClient.JSONMarshal = json.Marshal + return httpClient + }, + ), + + dal.Module, + + fx.Provide(cache.NewRedisCache, zap.NewNop), + )..., + ).Err() + + require.NoError(tb, err) +} diff --git a/internal/pkg/test/gorm.go b/internal/pkg/test/gorm.go index 83985d504..acb2f3fdc 100644 --- a/internal/pkg/test/gorm.go +++ b/internal/pkg/test/gorm.go @@ -44,7 +44,7 @@ func GetQuery(tb testing.TB) *query.Query { func GetGorm(tb testing.TB) *gorm.DB { tb.Helper() - RequireEnv(tb, EnvRedis) + RequireEnv(tb, EnvMysql) cfg, err := config.NewAppConfig() require.NoError(tb, err) @@ -56,7 +56,7 @@ func GetGorm(tb testing.TB) *gorm.DB { func newGorm(tb testing.TB, c config.AppConfig) (*gorm.DB, error) { tb.Helper() - conn, err := driver.NewMysqlConnectionPool(c) + conn, err := driver.NewMysqlDriver(c) if err != nil { return nil, errgo.Wrap(err, "sql.Open") } diff --git a/internal/pkg/test/redis.go b/internal/pkg/test/redis.go index 1ae557634..ab61e0969 100644 --- a/internal/pkg/test/redis.go +++ b/internal/pkg/test/redis.go @@ -17,22 +17,15 @@ package test import ( "testing" - "github.com/redis/go-redis/v9" - "github.com/stretchr/testify/require" - - "github.com/bangumi/server/config" - "github.com/bangumi/server/internal/pkg/driver" + "github.com/redis/rueidis" + "go.uber.org/fx" ) -func GetRedis(tb testing.TB) *redis.Client { +func GetRedis(tb testing.TB) (r rueidis.Client) { tb.Helper() - RequireEnv(tb, EnvRedis) - cfg, err := config.NewAppConfig() - require.NoError(tb, err) - db, err := driver.NewRedisClient(cfg) - require.NoError(tb, err) + Fx(tb, fx.Populate(&r)) - return db + return } diff --git a/internal/pkg/test/web.go b/internal/pkg/test/web.go index 7c3a60b41..10a891747 100644 --- a/internal/pkg/test/web.go +++ b/internal/pkg/test/web.go @@ -20,7 +20,7 @@ import ( "github.com/go-resty/resty/v2" "github.com/jarcoal/httpmock" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/stretchr/testify/mock" "go.uber.org/fx" @@ -34,41 +34,38 @@ import ( "github.com/bangumi/server/internal/index" "github.com/bangumi/server/internal/mocks" "github.com/bangumi/server/internal/model" - "github.com/bangumi/server/internal/notification" "github.com/bangumi/server/internal/person" "github.com/bangumi/server/internal/pkg/cache" "github.com/bangumi/server/internal/pkg/dam" + "github.com/bangumi/server/internal/pkg/driver" "github.com/bangumi/server/internal/pkg/logger" - "github.com/bangumi/server/internal/pm" "github.com/bangumi/server/internal/revision" "github.com/bangumi/server/internal/search" "github.com/bangumi/server/internal/subject" + "github.com/bangumi/server/internal/tag" "github.com/bangumi/server/internal/timeline" "github.com/bangumi/server/internal/user" "github.com/bangumi/server/web" "github.com/bangumi/server/web/handler" - "github.com/bangumi/server/web/session" ) type Mock struct { - SubjectRepo subject.Repo - SubjectCachedRepo subject.CachedRepo - PersonRepo person.Repo - CharacterRepo character.Repo - AuthRepo auth.Repo - AuthService auth.Service - EpisodeRepo episode.Repo - UserRepo user.Repo - IndexRepo index.Repo - RevisionRepo revision.Repo - CollectionRepo collections.Repo - TimeLineSrv timeline.Service - SessionManager session.Manager - Cache cache.RedisCache - PrivateMessageRepo pm.Repo - NotificationRepo notification.Repo - HTTPMock *httpmock.MockTransport - Dam *dam.Dam + SubjectRepo subject.Repo + SubjectCachedRepo subject.CachedRepo + PersonRepo person.Repo + CharacterRepo character.Repo + AuthRepo auth.Repo + TagRepo tag.Repo + AuthService auth.Service + EpisodeRepo episode.Repo + UserRepo user.Repo + IndexRepo index.Repo + RevisionRepo revision.Repo + CollectionRepo collections.Repo + TimeLineSrv timeline.Service + Cache cache.RedisCache + HTTPMock *httpmock.MockTransport + Dam *dam.Dam } //nolint:funlen @@ -104,15 +101,15 @@ func GetWebApp(tb testing.TB, m Mock) *echo.Echo { MockUserRepo(m.UserRepo), MockIndexRepo(m.IndexRepo), MockRevisionRepo(m.RevisionRepo), - MockPrivateMessageRepo(m.PrivateMessageRepo), - MockNoticationRepo(m.NotificationRepo), - MockSessionManager(m.SessionManager), MockTimeLineSrv(m.TimeLineSrv), + MockTagRepo(m.TagRepo), // don't need a default mock for these repositories. fx.Provide(func() collections.Repo { return m.CollectionRepo }), fx.Provide(func() search.Handler { return search.NoopClient{} }), + fx.Provide(driver.NewRueidisClient), + fx.Invoke(web.AddRouters), fx.Populate(&e), @@ -158,32 +155,6 @@ func MockIndexRepo(repo index.Repo) fx.Option { return fx.Supply(fx.Annotate(repo, fx.As(new(index.Repo)))) } -func MockPrivateMessageRepo(repo pm.Repo) fx.Option { - if repo == nil { - repo = &mocks.PrivateMessageRepo{} - } - return fx.Supply(fx.Annotate(repo, fx.As(new(pm.Repo)))) -} - -func MockNoticationRepo(repo notification.Repo) fx.Option { - if repo == nil { - repo = &mocks.NotificationRepo{} - } - return fx.Supply(fx.Annotate(repo, fx.As(new(notification.Repo)))) -} - -func MockSessionManager(repo session.Manager) fx.Option { - if repo == nil { - mocker := &mocks.SessionManager{} - mocker.EXPECT().Create(mock.Anything, mock.Anything).Return("mocked random string", session.Session{}, nil) - mocker.EXPECT().Get(mock.Anything, mock.Anything).Return(session.Session{}, nil) - - repo = mocker - } - - return fx.Provide(func() session.Manager { return repo }) -} - func MockUserRepo(repo user.Repo) fx.Option { if repo == nil { return fx.Provide(AnyUserMock) @@ -237,6 +208,18 @@ func MockAuthRepo(m auth.Repo) fx.Option { return fx.Provide(func() auth.Repo { return m }) } +func MockTagRepo(m tag.Repo) fx.Option { + if m == nil { + mocker := &mocks.TagRepo{} + mocker.EXPECT().Get(mock.Anything, mock.Anything, mock.Anything).Return([]tag.Tag{}, nil) + mocker.EXPECT().GetByIDs(mock.Anything, mock.Anything).Return(map[model.SubjectID][]tag.Tag{}, nil) + + m = mocker + } + + return fx.Provide(func() tag.Repo { return m }, func() tag.CachedRepo { return m }) +} + func MockAuthService(m auth.Service) fx.Option { if m == nil { return fx.Provide(auth.NewService) @@ -269,11 +252,12 @@ func MockSubjectReadRepo(m subject.CachedRepo) fx.Option { func MockTimeLineSrv(m timeline.Service) fx.Option { if m == nil { - mocker := &mocks.TimeLineService{} + mocker := &mocks.TimelineService{} mocker.EXPECT().ChangeSubjectCollection(mock.Anything, mock.Anything, - mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) - mocker.EXPECT().ChangeEpisodeStatus(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) + mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) + mocker.EXPECT(). + ChangeEpisodeStatus(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) mocker.EXPECT().ChangeSubjectProgress(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) diff --git a/internal/pkg/tools/tool.go b/internal/pkg/tools/tool.go deleted file mode 100644 index 61fb8ca4f..000000000 --- a/internal/pkg/tools/tool.go +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -//go:build tools - -package tools - -import ( - _ "github.com/vektra/mockery/v2" - _ "google.golang.org/grpc/cmd/protoc-gen-go-grpc" - _ "google.golang.org/protobuf/cmd/protoc-gen-go" -) diff --git a/internal/pm/domain.go b/internal/pm/domain.go deleted file mode 100644 index 2b04984a1..000000000 --- a/internal/pm/domain.go +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package pm - -import ( - "context" - - "github.com/bangumi/server/internal/model" - "github.com/bangumi/server/internal/pkg/null" -) - -type Repo interface { - List( - ctx context.Context, - userID model.UserID, - folder FolderType, - offset int, - limit int, - ) ([]PrivateMessageListItem, error) - - CountByFolder( - ctx context.Context, - userID model.UserID, - folder FolderType, - ) (int64, error) - - ListRelated( - ctx context.Context, - userID model.UserID, - id model.PrivateMessageID, - ) ([]PrivateMessage, error) - - CountTypes(ctx context.Context, userID model.UserID) (PrivateMessageTypeCounts, error) - - MarkRead(ctx context.Context, userID model.UserID, relatedID model.PrivateMessageID) error - - ListRecentContact(ctx context.Context, userID model.UserID) ([]model.UserID, error) - - Create( - ctx context.Context, - senderID model.UserID, - receiverIDs []model.UserID, - relatedIDFilter IDFilter, - title string, - content string, - ) ([]PrivateMessage, error) - - Delete( - ctx context.Context, - userID model.UserID, - ids []model.PrivateMessageID, - ) error -} - -type IDFilter struct { - Type null.Null[model.PrivateMessageID] -} diff --git a/internal/pm/error.go b/internal/pm/error.go deleted file mode 100644 index 88e7fd7f3..000000000 --- a/internal/pm/error.go +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package pm - -import ( - "errors" -) - -var ErrPmNotOwned = errors.New("not sent or received this private message") -var ErrPmDeleted = errors.New("private message deleted") -var ErrPmUserIrrelevant = errors.New("has user irrelevant message") -var ErrPmRelatedNotExists = errors.New("related private message not exists") -var ErrPmInvalidOperation = errors.New("invalid operation") diff --git a/internal/pm/model.go b/internal/pm/model.go deleted file mode 100644 index b0d210ae7..000000000 --- a/internal/pm/model.go +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package pm - -import ( - "time" - - "github.com/bangumi/server/internal/model" -) - -type FolderType string - -const ( - FolderTypeInbox FolderType = "inbox" - FolderTypeOutbox FolderType = "outbox" -) - -type PrivateMessage struct { - CreatedTime time.Time - Title string - Content string - Folder FolderType - SenderID model.UserID - ReceiverID model.UserID - ID model.PrivateMessageID - MainMessageID model.PrivateMessageID // 如果当前是首条私信,则为当前私信的id,否则为0 - RelatedMessageID model.PrivateMessageID // 首条私信的id - New bool - DeletedBySender bool - DeletedByReceiver bool -} - -type PrivateMessageListItem struct { - Main PrivateMessage - Self PrivateMessage -} - -type PrivateMessageTypeCounts struct { - Unread int64 - Inbox int64 - Outbox int64 -} diff --git a/internal/pm/mysql_repository.go b/internal/pm/mysql_repository.go deleted file mode 100644 index 4e519a8e2..000000000 --- a/internal/pm/mysql_repository.go +++ /dev/null @@ -1,492 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package pm - -import ( - "context" - "time" - - "github.com/samber/lo" - "github.com/trim21/errgo" - "go.uber.org/zap" - "gorm.io/gen" - - "github.com/bangumi/server/dal/dao" - "github.com/bangumi/server/dal/query" - "github.com/bangumi/server/domain/gerr" - "github.com/bangumi/server/internal/model" - "github.com/bangumi/server/internal/pkg/generic/slice" - "github.com/bangumi/server/internal/pkg/null" -) - -const recentContactLimit = 15 - -type mysqlRepo struct { - q *query.Query - log *zap.Logger -} - -func NewMysqlRepo(q *query.Query, log *zap.Logger) (Repo, error) { - return mysqlRepo{q: q, log: log.Named("pm.mysqlRepo")}, nil -} - -func (r mysqlRepo) List( - ctx context.Context, - userID model.UserID, - folder FolderType, - offset int, - limit int, -) ([]PrivateMessageListItem, error) { - var conds []gen.Condition - do := r.q.PrivateMessage.WithContext(ctx) - if folder == FolderTypeInbox { - conds = []gen.Condition{ - r.q.PrivateMessage.ReceiverID.Eq(userID), - r.q.PrivateMessage.DeletedByReceiver.Is(false), - } - } else { - conds = []gen.Condition{ - r.q.PrivateMessage.SenderID.Eq(userID), - r.q.PrivateMessage.DeletedBySender.Is(false), - } - } - ret, err := do. - Where( - conds..., - ).Order(r.q.PrivateMessage.ID.Desc()).Offset(offset).Limit(limit).Find() - - if err != nil { - r.log.Error("unexpected error", zap.Error(err)) - return make([]PrivateMessageListItem, 0), errgo.Wrap(err, "dal") - } - - mainIDs := lo.Uniq(slice.Map(ret, func(v *dao.PrivateMessage) model.PrivateMessageID { - return v.RelatedMessageID - })) - - mainMsgList, err := do.Where(r.q.PrivateMessage.ID.In(mainIDs...)).Find() - if err != nil { - r.log.Error("unexpected error", zap.Error(err)) - return make([]PrivateMessageListItem, 0), errgo.Wrap(err, "dal") - } - mainMsgs := slice.ToMap(mainMsgList, func(v *dao.PrivateMessage) model.PrivateMessageID { - return v.ID - }) - return slice.Map(ret, func(v *dao.PrivateMessage) PrivateMessageListItem { - return PrivateMessageListItem{ - Main: convertDaoToModel(mainMsgs[v.RelatedMessageID]), - Self: convertDaoToModel(v), - } - }), nil -} - -func countByFolder(ctx context.Context, - q *query.Query, - userID model.UserID, - folder FolderType) (int64, error) { - var conds []gen.Condition - do := q.PrivateMessage.WithContext(ctx) - if folder == FolderTypeInbox { - conds = []gen.Condition{ - q.PrivateMessage.ReceiverID.Eq(userID), - q.PrivateMessage.DeletedByReceiver.Is(false), - } - } else { - conds = []gen.Condition{ - q.PrivateMessage.SenderID.Eq(userID), - q.PrivateMessage.DeletedBySender.Is(false), - } - } - count, err := do.Where(conds...).Count() - if err != nil { - return 0, errgo.Wrap(err, "dal") - } - return count, nil -} - -func (r mysqlRepo) CountByFolder(ctx context.Context, - userID model.UserID, - folder FolderType) (int64, error) { - count, err := countByFolder(ctx, r.q, userID, folder) - if err != nil { - r.log.Error("unexpected error", zap.Error(err)) - return 0, err - } - return count, nil -} - -func (r mysqlRepo) getMainMsg( - ctx context.Context, - id model.PrivateMessageID, -) (*dao.PrivateMessage, error) { - do := r.q.PrivateMessage.WithContext(ctx) - msg, err := do.Where(r.q.PrivateMessage.ID.Eq(id)).Take() - if err != nil { - return nil, errgo.Wrap(err, "dal") - } - if msg == nil { - return nil, gerr.ErrNotFound - } - if msg.MainMessageID == 0 { - if msg.RelatedMessageID == 0 { - return nil, gerr.ErrNotFound - } - return r.getMainMsg(ctx, msg.RelatedMessageID) - } - return msg, nil -} - -func (r mysqlRepo) ListRelated( - ctx context.Context, - userID model.UserID, - id model.PrivateMessageID, -) ([]PrivateMessage, error) { - do := r.q.PrivateMessage.WithContext(ctx) - firstMsg, err := r.getMainMsg(ctx, id) - var emptyMsgList = make([]PrivateMessage, 0) - if err != nil { - r.log.Error("unexpected error", zap.Error(err)) - return emptyMsgList, errgo.Wrap(err, "dal") - } - if firstMsg.SenderID != userID && firstMsg.ReceiverID != userID { - return emptyMsgList, ErrPmNotOwned - } - if (firstMsg.SenderID == userID && firstMsg.DeletedBySender) || - (firstMsg.ReceiverID == userID && firstMsg.DeletedByReceiver) { - return emptyMsgList, ErrPmDeleted - } - res, err := do.Where( - r.q.PrivateMessage.RelatedMessageID.Eq(firstMsg.ID), - do.Where( - do. - Where( - r.q.PrivateMessage.SenderID.Eq(userID), - r.q.PrivateMessage.DeletedBySender.Is(false), - ), - ). - Or( - r.q.PrivateMessage.ReceiverID.Eq(userID), - r.q.PrivateMessage.DeletedByReceiver.Is(false), - ), - ). - Find() - if err != nil { - r.log.Error("unexpected error", zap.Error(err)) - return emptyMsgList, errgo.Wrap(err, "dal") - } - return slice.Map(res, convertDaoToModel), nil -} - -func (r mysqlRepo) CountTypes( - ctx context.Context, - userID model.UserID, -) (PrivateMessageTypeCounts, error) { - do := r.q.PrivateMessage.WithContext(ctx) - res := PrivateMessageTypeCounts{} - c1, err := r.CountByFolder(ctx, userID, FolderTypeOutbox) - if err != nil { - return res, err - } - c2, err := r.CountByFolder(ctx, userID, FolderTypeInbox) - if err != nil { - return res, err - } - c3, err := do.Where( - r.q.PrivateMessage.ReceiverID.Eq(userID), - r.q.PrivateMessage.DeletedBySender.Is(false), - r.q.PrivateMessage.New.Is(true)). - Count() - if err != nil { - r.log.Error("unexpected error", zap.Error(err)) - return res, errgo.Wrap(err, "dal") - } - res.Outbox = c1 - res.Inbox = c2 - res.Unread = c3 - return res, nil -} - -func (r mysqlRepo) ListRecentContact( - ctx context.Context, - userID model.UserID, -) ([]model.UserID, error) { - res, err := r.q.PrivateMessage. - WithContext(ctx). - Select(r.q.PrivateMessage.ReceiverID). - Where(r.q.PrivateMessage.SenderID.Eq(userID)). - Order(r.q.PrivateMessage.CreatedTime.Desc()). - Group(r.q.PrivateMessage.ReceiverID). - Limit(recentContactLimit). - Find() - if err != nil { - r.log.Error("unexpected error", zap.Error(err)) - return make([]model.UserID, 0), errgo.Wrap(err, "dal") - } - return slice.Map(res, func(v *dao.PrivateMessage) model.UserID { - return v.ReceiverID - }), nil -} - -func (r mysqlRepo) MarkRead(ctx context.Context, userID model.UserID, relatedID model.PrivateMessageID) error { - var affectedRows int64 - err := r.q.Transaction(func(tx *query.Query) error { - txCtx := tx.WithContext(ctx) - rows, err := txCtx.PrivateMessage. - Where( - tx.PrivateMessage.RelatedMessageID.Eq(relatedID), - tx.PrivateMessage.ReceiverID.Eq(userID), - tx.PrivateMessage.New.Is(true)). - Update(r.q.PrivateMessage.New, false) - if err != nil { - return errgo.Wrap(err, "dal") - } - affectedRows = rows.RowsAffected - if rows.RowsAffected != 0 { - count, err := countByFolder(ctx, tx, userID, FolderTypeInbox) - if err != nil { - return errgo.Wrap(err, "dal") - } - if count == 0 { - _, err = txCtx.Member.Where(tx.Member.ID.Eq(userID)).Update(tx.Member.Newpm, false) - if err != nil { - return errgo.Wrap(err, "dal") - } - } - } - return nil - }) - - r.q.Member.WithContext(ctx) - - if err != nil { - r.log.Error("unexpected error", zap.Error(err)) - return errgo.Wrap(err, "dal") - } - if affectedRows == 0 { - return ErrPmInvalidOperation - } - return nil -} - -func (r mysqlRepo) constructMsgs( - senderID model.UserID, - receiverIDs []model.UserID, - relatedIDFilter IDFilter, - title string, - content string, -) []*dao.PrivateMessage { - msgs := make([]*dao.PrivateMessage, len(receiverIDs)) - for i := range msgs { - msgs[i] = &dao.PrivateMessage{ - SenderID: senderID, - ReceiverID: receiverIDs[i], - Title: title, - Content: content, - New: true, - CreatedTime: uint32(time.Now().Unix()), - } - if relatedIDFilter.Type.Set { - msgs[i].RelatedMessageID = relatedIDFilter.Type.Value - } - } - return msgs -} - -func (r mysqlRepo) Create( - ctx context.Context, - senderID model.UserID, - receiverIDs []model.UserID, - relatedIDFilter IDFilter, - title string, - content string, -) ([]PrivateMessage, error) { - emptyList := make([]PrivateMessage, 0) - if relatedIDFilter.Type.Set { - if len(receiverIDs) > 1 { - return emptyList, ErrPmInvalidOperation - } - msg, err := r.getMainMsg(ctx, relatedIDFilter.Type.Value) - if (err != nil || msg.SenderID != senderID && msg.SenderID != receiverIDs[0]) || - (msg.ReceiverID != senderID && msg.ReceiverID != receiverIDs[0]) { - return emptyList, ErrPmRelatedNotExists - } - if msg.ID != relatedIDFilter.Type.Value { - relatedIDFilter = IDFilter{Type: null.New(msg.ID)} - } - } - msgs := r.constructMsgs(senderID, receiverIDs, relatedIDFilter, title, content) - res := emptyList - err := r.q.Transaction(func(tx *query.Query) error { - txCtx := tx.WithContext(ctx) - err := txCtx.PrivateMessage.Create(msgs...) - if err != nil { - return errgo.Wrap(err, "dal") - } - _, err = txCtx.Member.Where(tx.Member.ID.In(receiverIDs...)).Update(tx.Member.Newpm, true) - if err != nil { - return errgo.Wrap(err, "dal") - } - if !relatedIDFilter.Type.Set { - for i := range msgs { - msgs[i].MainMessageID = msgs[i].ID - msgs[i].RelatedMessageID = msgs[i].ID - } - err = txCtx.PrivateMessage.Save(msgs...) - if err != nil { - return errgo.Wrap(err, "dal") - } - } - res = slice.Map(msgs, convertDaoToModel) - return nil - }) - if err != nil { - r.log.Error("unexpected error", zap.Error(err)) - return res, errgo.Wrap(err, "dal") - } - return res, err -} - -func (r mysqlRepo) Delete( - ctx context.Context, - userID model.UserID, - ids []model.PrivateMessageID, -) error { - do := r.q.PrivateMessage.WithContext(ctx) - pms, err := do. - Where( - r.q.PrivateMessage.ID.In(ids...), - do.Where( - r.q.PrivateMessage.SenderID.Eq(userID)).Or(r.q.PrivateMessage.ReceiverID.Eq(userID)), - ).Find() - if err != nil { - r.log.Error("unexpected error", zap.Error(err)) - return errgo.Wrap(err, "dal") - } - if len(pms) != len(ids) { - return ErrPmUserIrrelevant - } - err = r.q.Transaction(func(tx *query.Query) error { - err = handleReplyDeletes(ctx, tx, pms, userID) - if err != nil { - r.log.Error("unexpected error", zap.Error(err)) - return err - } - err = handleMainDeletes(ctx, tx, pms, userID) - if err != nil { - r.log.Error("unexpected error", zap.Error(err)) - return err - } - return nil - }) - - if err != nil { - r.log.Error("unexpected error", zap.Error(err)) - return errgo.Wrap(err, "dal") - } - - return nil -} - -func handleReplyDeletes(ctx context.Context, tx *query.Query, pms []*dao.PrivateMessage, userID model.UserID) error { - senderDeletes := slice.MapFilter(pms, func(v *dao.PrivateMessage) (uint32, bool) { - ok := !v.DeletedBySender && v.MainMessageID == 0 && v.SenderID == userID - if ok { - return v.ID, ok - } - return 0, false - }) - receiverDeletes := slice.MapFilter(pms, func(v *dao.PrivateMessage) (uint32, bool) { - ok := !v.DeletedByReceiver && v.MainMessageID == 0 && v.ReceiverID == userID - if ok { - return v.ID, ok - } - return 0, false - }) - txCtx := tx.WithContext(ctx) - if len(senderDeletes) != 0 { - _, err := txCtx.PrivateMessage.Where( - tx.PrivateMessage.ID.In(senderDeletes...), - ).Update(tx.PrivateMessage.DeletedBySender, true) - if err != nil { - return errgo.Wrap(err, "dal") - } - } - if len(receiverDeletes) != 0 { - _, err := txCtx.PrivateMessage.Where( - tx.PrivateMessage.ID.In(receiverDeletes...), - ).Update(tx.PrivateMessage.DeletedByReceiver, true) - if err != nil { - return errgo.Wrap(err, "dal") - } - } - return nil -} - -func handleMainDeletes(ctx context.Context, tx *query.Query, pms []*dao.PrivateMessage, userID model.UserID) error { - senderDeletes := slice.MapFilter(pms, func(v *dao.PrivateMessage) (uint32, bool) { - ok := v.MainMessageID != 0 && v.SenderID == userID - if ok { - return v.ID, ok - } - return 0, false - }) - receiverDeletes := slice.MapFilter(pms, func(v *dao.PrivateMessage) (uint32, bool) { - ok := v.MainMessageID != 0 && v.ReceiverID == userID - if ok { - return v.ID, ok - } - return 0, false - }) - txCtx := tx.WithContext(ctx) - if len(senderDeletes) != 0 { - _, err := txCtx.PrivateMessage.Where( - tx.PrivateMessage.RelatedMessageID.In(senderDeletes...), - tx.PrivateMessage.DeletedBySender.Is(false), - ).Update(tx.PrivateMessage.DeletedBySender, true) - if err != nil { - return errgo.Wrap(err, "dal") - } - } - if len(receiverDeletes) != 0 { - _, err := txCtx.PrivateMessage.Where( - tx.PrivateMessage.RelatedMessageID.In(receiverDeletes...), - tx.PrivateMessage.DeletedByReceiver.Is(false), - ).Update(tx.PrivateMessage.DeletedByReceiver, true) - if err != nil { - return errgo.Wrap(err, "dal") - } - } - return nil -} - -func convertDaoToModel(d *dao.PrivateMessage) PrivateMessage { - if d == nil { - return PrivateMessage{} - } - return PrivateMessage{ - CreatedTime: time.Unix(int64(d.CreatedTime), 0), - Title: d.Title, - Content: d.Content, - Folder: FolderType(d.Folder), - SenderID: d.SenderID, - ReceiverID: d.ReceiverID, - ID: d.ID, - MainMessageID: d.MainMessageID, - RelatedMessageID: d.RelatedMessageID, - New: d.New, - DeletedBySender: d.DeletedBySender, - DeletedByReceiver: d.DeletedByReceiver, - } -} diff --git a/internal/pm/mysql_repository_test.go b/internal/pm/mysql_repository_test.go deleted file mode 100644 index 5b2483386..000000000 --- a/internal/pm/mysql_repository_test.go +++ /dev/null @@ -1,263 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package pm_test - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - "go.uber.org/zap" - - "github.com/bangumi/server/dal/query" - "github.com/bangumi/server/internal/model" - "github.com/bangumi/server/internal/pkg/generic/slice" - "github.com/bangumi/server/internal/pkg/null" - "github.com/bangumi/server/internal/pkg/test" - "github.com/bangumi/server/internal/pm" -) - -func getRepo(t *testing.T) pm.Repo { - t.Helper() - repo, err := pm.NewMysqlRepo(query.Use(test.GetGorm(t)), zap.NewNop()) - require.NoError(t, err) - - return repo -} - -func mapToID(msg pm.PrivateMessage) model.PrivateMessageID { - return msg.ID -} - -func mockMessage( - ctx context.Context, - t *testing.T, - repo pm.Repo, - relatedID *model.PrivateMessageID, - senderID model.UserID, - receiverID model.UserID, -) pm.PrivateMessage { - t.Helper() - m, err := repo.Create( - ctx, - senderID, - []model.UserID{receiverID}, - pm.IDFilter{Type: null.NewFromPtr(relatedID)}, - "title", - "content", - ) - require.NoError(t, err) - require.NotEmpty(t, m) - t.Cleanup(func() { - err = repo.Delete(ctx, senderID, slice.Map(m, mapToID)) - require.NoError(t, err) - err = repo.Delete(ctx, receiverID, slice.Map(m, mapToID)) - require.NoError(t, err) - }) - return m[0] -} - -func TestListInbox(t *testing.T) { - test.RequireEnv(t, test.EnvMysql) - t.Parallel() - - repo := getRepo(t) - - ctx := context.Background() - - m := mockMessage(ctx, t, repo, nil, 1, 382951) - - list, err := repo.List(ctx, 382951, pm.FolderTypeInbox, 0, 10) - require.NoError(t, err) - require.NotEmpty(t, list) - require.LessOrEqual(t, m.ID, list[0].Self.ID) -} - -func TestListOutbox(t *testing.T) { - test.RequireEnv(t, test.EnvMysql) - t.Parallel() - - repo := getRepo(t) - - ctx := context.Background() - - m := mockMessage(ctx, t, repo, nil, 1, 382951) - - list, err := repo.List(ctx, 1, pm.FolderTypeOutbox, 0, 10) - require.NoError(t, err) - require.NotEmpty(t, list) - require.LessOrEqual(t, m.ID, list[0].Self.ID) -} - -func TestListRelated(t *testing.T) { - test.RequireEnv(t, test.EnvMysql) - t.Parallel() - - repo := getRepo(t) - - ctx := context.Background() - msg := mockMessage(ctx, t, repo, nil, 1, 382951) - - msg2 := mockMessage(ctx, t, repo, &msg.ID, 382951, 1) - - list, err := repo.ListRelated(ctx, 1, msg.ID) - require.NoError(t, err) - require.Len(t, list, 2) - require.Equal(t, msg2.ID, list[len(list)-1].ID) - - // 使用非首条信息作查询 - list, err = repo.ListRelated(ctx, 382951, msg2.ID) - require.NoError(t, err) - require.Len(t, list, 2) - require.Equal(t, msg.ID, list[0].ID) -} - -func TestCountTypes(t *testing.T) { - test.RequireEnv(t, test.EnvMysql) - t.Parallel() - - repo := getRepo(t) - ctx := context.Background() - prevCounts, err := repo.CountTypes(ctx, 5) - require.NoError(t, err) - prevCounts2, err := repo.CountTypes(ctx, 4) - require.NoError(t, err) - mockMessage(ctx, t, repo, nil, 5, 4) - counts, err := repo.CountTypes(ctx, 5) - require.NoError(t, err) - counts2, err := repo.CountTypes(ctx, 4) - require.NoError(t, err) - require.Less(t, prevCounts.Outbox, counts.Outbox) - require.Less(t, prevCounts2.Unread, counts2.Unread) - require.Less(t, prevCounts2.Inbox, counts2.Inbox) -} - -func TestListRecentContact(t *testing.T) { - test.RequireEnv(t, test.EnvMysql) - t.Parallel() - - repo := getRepo(t) - - ctx := context.Background() - - mockMessage(ctx, t, repo, nil, 1, 382951) - - list, err := repo.ListRecentContact(ctx, 1) - require.NoError(t, err) - require.NotEmpty(t, list) - require.Contains(t, list, model.UserID(382951)) -} - -func TestMarkRead(t *testing.T) { - test.RequireEnv(t, test.EnvMysql) - t.Parallel() - - repo := getRepo(t) - - ctx := context.Background() - msg := mockMessage(ctx, t, repo, nil, 1, 382951) - err := repo.MarkRead(ctx, 382951, msg.ID) - require.NoError(t, err) - msgs, err := repo.ListRelated(ctx, 1, msg.ID) - require.NoError(t, err) - require.Len(t, msgs, 1) - require.Equal(t, false, msgs[0].New) -} - -func TestCreate(t *testing.T) { - test.RequireEnv(t, test.EnvMysql) - t.Parallel() - - repo := getRepo(t) - - ctx := context.Background() - - mainMsgs, err := repo.Create( - ctx, - 1, - []model.UserID{382951, 2}, - pm.IDFilter{Type: null.NewFromPtr[model.PrivateMessageID](nil)}, - "私信", - "内容", - ) - require.NoError(t, err) - require.Len(t, mainMsgs, 2) - - msgs := mainMsgs - - // reply - replyMsgs, err := repo.Create(ctx, 382951, []model.UserID{1}, pm.IDFilter{ - Type: null.New(mainMsgs[0].ID), - }, "私信回复", "内容") - - require.NoError(t, err) - require.Len(t, replyMsgs, 1) - require.Equal(t, mainMsgs[0].ID, replyMsgs[0].RelatedMessageID) - - msgs = append(msgs, replyMsgs...) - - _, err = repo.Create(ctx, 382951, []model.UserID{2}, pm.IDFilter{ - Type: null.New(mainMsgs[1].ID), - }, "私信回复", "发给错误的人") - - require.Error(t, err) - - msgsNotMain, err := repo.Create(ctx, 1, []model.UserID{382951}, pm.IDFilter{ - Type: null.New(replyMsgs[0].ID), - }, "私信回复", "使用非首条信息的id作为related id") - - require.NoError(t, err) - require.Len(t, msgsNotMain, 1) - require.Equal(t, mainMsgs[0].ID, msgsNotMain[0].RelatedMessageID) - msgs = append(msgs, msgsNotMain...) - - t.Cleanup(func() { - for _, msg := range msgs { - err = repo.Delete(ctx, msg.SenderID, slice.Map([]pm.PrivateMessage{msg}, mapToID)) - require.NoError(t, err) - err = repo.Delete(ctx, msg.ReceiverID, slice.Map([]pm.PrivateMessage{msg}, mapToID)) - require.NoError(t, err) - } - }) -} - -func TestDelete(t *testing.T) { - test.RequireEnv(t, test.EnvMysql) - t.Parallel() - - repo := getRepo(t) - ctx := context.Background() - res, err := repo.Create( - ctx, - 1, - []model.UserID{382951}, - pm.IDFilter{Type: null.NewFromPtr[model.PrivateMessageID](nil)}, - "私信", - "内容", - ) - require.NoError(t, err) - require.Len(t, res, 1) - err = repo.Delete(ctx, 1, []model.PrivateMessageID{res[0].ID}) - require.NoError(t, err) - _, err = repo.ListRelated(ctx, 1, res[0].ID) - require.Error(t, err) - res, err = repo.ListRelated(ctx, 382951, res[0].ID) - require.NoError(t, err) - require.Len(t, res, 1) - t.Cleanup(func() { - err := repo.Delete(ctx, 382951, []model.PrivateMessageID{res[0].ID}) - require.NoError(t, err) - }) -} diff --git a/internal/revision/mysql_repo_episode.go b/internal/revision/mysql_repo_episode.go index 291098469..a47827f67 100644 --- a/internal/revision/mysql_repo_episode.go +++ b/internal/revision/mysql_repo_episode.go @@ -76,7 +76,7 @@ func (r mysqlRepo) GetEpisodeRelated(ctx context.Context, id model.RevisionID) ( r.log.Error("can't find revision text", zap.Uint32("id", revision.TextID)) return model.EpisodeRevision{}, gerr.ErrNotFound } - r.log.Error("unexpected error happened", zap.Error(err)) + return model.EpisodeRevision{}, errgo.Wrap(err, "dal") } diff --git a/internal/revision/mysql_repository.go b/internal/revision/mysql_repository.go index 37b975c3d..5bcbd081e 100644 --- a/internal/revision/mysql_repository.go +++ b/internal/revision/mysql_repository.go @@ -24,7 +24,6 @@ import ( "reflect" "time" - "github.com/elliotchance/phpserialize" "github.com/mitchellh/mapstructure" "github.com/trim21/errgo" "go.uber.org/zap" @@ -34,6 +33,7 @@ import ( "github.com/bangumi/server/dal/query" "github.com/bangumi/server/domain/gerr" "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/pkg/serialize" ) type mysqlRepo struct { @@ -84,7 +84,7 @@ func (r mysqlRepo) GetPersonRelated(ctx context.Context, id model.RevisionID) (m if errors.Is(err, gorm.ErrRecordNotFound) { return model.PersonRevision{}, gerr.ErrNotFound } - r.log.Error("unexpected error happened", zap.Error(err)) + return model.PersonRevision{}, errgo.Wrap(err, "dal") } data, err := r.q.RevisionText.WithContext(ctx). @@ -95,7 +95,6 @@ func (r mysqlRepo) GetPersonRelated(ctx context.Context, id model.RevisionID) (m return model.PersonRevision{}, gerr.ErrNotFound } - r.log.Error("unexpected error happened", zap.Error(err)) return model.PersonRevision{}, errgo.Wrap(err, "dal") } return convertPersonRevisionDao(revision, data), nil @@ -148,7 +147,7 @@ func (r mysqlRepo) GetCharacterRelated(ctx context.Context, id model.RevisionID) r.log.Error("can't find revision text", zap.Uint32("id", revision.TextID)) return model.CharacterRevision{}, gerr.ErrNotFound } - r.log.Error("unexpected error happened", zap.Error(err)) + return model.CharacterRevision{}, errgo.Wrap(err, "dal") } return convertCharacterRevisionDao(revision, data), nil @@ -199,7 +198,6 @@ func (r mysqlRepo) GetSubjectRelated(ctx context.Context, id model.RevisionID) ( return model.SubjectRevision{}, gerr.ErrNotFound } - r.log.Error("unexpected error happened", zap.Error(err)) return model.SubjectRevision{}, errgo.Wrap(err, "dal") } return convertSubjectRevisionDao(revision, true), nil @@ -239,7 +237,8 @@ func convertRevisionText(text []byte) map[string]any { if err != nil { return nil } - result, err := phpserialize.UnmarshalAssociativeArray(b) + var result map[any]any + err = serialize.Decode(b, &result) if err != nil { return nil } diff --git a/internal/search/character/client.go b/internal/search/character/client.go new file mode 100644 index 000000000..d6ac994da --- /dev/null +++ b/internal/search/character/client.go @@ -0,0 +1,99 @@ +package character + +import ( + "context" + "fmt" + "reflect" + "strconv" + + "github.com/meilisearch/meilisearch-go" + "github.com/trim21/errgo" + "go.uber.org/zap" + + "github.com/bangumi/server/config" + "github.com/bangumi/server/dal/query" + "github.com/bangumi/server/internal/character" + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/search/searcher" +) + +const ( + idx = "characters" +) + +func New( + cfg config.AppConfig, + meili meilisearch.ServiceManager, + repo character.Repo, + log *zap.Logger, + query *query.Query, +) (searcher.Searcher, error) { + if repo == nil { + return nil, fmt.Errorf("nil characterRepo") + } + c := &client{ + meili: meili, + repo: repo, + index: meili.Index(idx), + log: log.Named("search").With(zap.String("index", idx)), + q: query, + } + + if cfg.AppType != config.AppTypeCanal { + return c, nil + } + + return c, c.canalInit(cfg) +} + +type client struct { + repo character.Repo + index meilisearch.IndexManager + + meili meilisearch.ServiceManager + log *zap.Logger + q *query.Query +} + +func (c *client) canalInit(cfg config.AppConfig) error { + if err := searcher.ValidateConfigs(cfg); err != nil { + return errgo.Wrap(err, "validate search config") + } + shouldCreateIndex, err := searcher.NeedFirstRun(c.meili, idx) + if err != nil { + return err + } + if shouldCreateIndex { + go c.firstRun() + } + return nil +} + +//nolint:funlen +func (c *client) firstRun() { + c.log.Info("search initialize") + rt := reflect.TypeOf(document{}) + searcher.InitIndex(c.log, c.meili, idx, rt, rankRule()) + + ctx := context.Background() + + maxItem, err := c.q.Character.WithContext(ctx).Limit(1).Order(c.q.Character.ID.Desc()).Take() + if err != nil { + c.log.Fatal("failed to get current max id", zap.Error(err)) + return + } + + c.log.Info(fmt.Sprintf("run full search index with max %s id %d", idx, maxItem.ID)) + + width := len(strconv.Itoa(int(maxItem.ID))) + for i := model.CharacterID(1); i <= maxItem.ID; i++ { + if i%10000 == 0 { + c.log.Info(fmt.Sprintf("progress %*d/%d", width, i, maxItem.ID)) + } + + err := c.OnUpdate(ctx, i) + if err != nil { + c.log.Error("error when updating", zap.Error(err)) + } + } +} diff --git a/internal/search/character/doc.go b/internal/search/character/doc.go new file mode 100644 index 000000000..7a732aeea --- /dev/null +++ b/internal/search/character/doc.go @@ -0,0 +1,52 @@ +package character + +import ( + "strconv" + + wiki "github.com/bangumi/wiki-parser-go" + + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/search/searcher" +) + +type document struct { + ID model.CharacterID `json:"id"` + Name string `json:"name" searchable:"true"` + Aliases []string `json:"aliases,omitempty" searchable:"true"` + Comment uint32 `json:"comment" sortable:"true"` + Collect uint32 `json:"collect" sortable:"true"` + NSFW bool `json:"nsfw" filterable:"true"` +} + +func (d *document) GetID() string { + return strconv.FormatUint(uint64(d.ID), 10) +} + +func rankRule() *[]string { + return &[]string{ + // 相似度最优先 + "exactness", + "words", + "typo", + "proximity", + "attribute", + "sort", + "id:asc", + "comment:desc", + "collect:desc", + "nsfw:asc", + } +} + +func extract(c *model.Character) searcher.Document { + w := wiki.ParseOmitError(c.Infobox) + + return &document{ + ID: c.ID, + Name: c.Name, + Aliases: searcher.ExtractAliases(w), + Comment: c.CommentCount, + Collect: c.CollectCount, + NSFW: c.NSFW, + } +} diff --git a/internal/search/character/event.go b/internal/search/character/event.go new file mode 100644 index 000000000..b78712620 --- /dev/null +++ b/internal/search/character/event.go @@ -0,0 +1,59 @@ +package character + +import ( + "context" + "errors" + "strconv" + + "github.com/meilisearch/meilisearch-go" + "github.com/samber/lo" + "github.com/trim21/errgo" + + "github.com/bangumi/server/domain/gerr" + "github.com/bangumi/server/internal/model" +) + +func (c *client) OnAdded(ctx context.Context, id model.CharacterID) error { + s, err := c.repo.Get(ctx, id) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return nil + } + return errgo.Wrap(err, "characterRepo.Get") + } + + if s.Redirect != 0 { + return c.OnDelete(ctx, id) + } + + extracted := extract(&s) + + _, err = c.index.UpdateDocumentsWithContext(ctx, extracted, &meilisearch.DocumentOptions{PrimaryKey: lo.ToPtr("id")}) + return err +} + +func (c *client) OnUpdate(ctx context.Context, id model.CharacterID) error { + s, err := c.repo.Get(ctx, id) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return nil + } + return errgo.Wrap(err, "characterRepo.Get") + } + + if s.Redirect != 0 { + return c.OnDelete(ctx, id) + } + + extracted := extract(&s) + + _, err = c.index.UpdateDocumentsWithContext(ctx, extracted, &meilisearch.DocumentOptions{PrimaryKey: lo.ToPtr("id")}) + + return err +} + +func (c *client) OnDelete(ctx context.Context, id model.CharacterID) error { + _, err := c.index.DeleteDocumentWithContext(ctx, strconv.FormatUint(uint64(id), 10), nil) + + return errgo.Wrap(err, "search") +} diff --git a/internal/search/character/handle.go b/internal/search/character/handle.go new file mode 100644 index 000000000..632804879 --- /dev/null +++ b/internal/search/character/handle.go @@ -0,0 +1,128 @@ +package character + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/labstack/echo/v5" + "github.com/meilisearch/meilisearch-go" + "github.com/trim21/errgo" + + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/pkg/generic/slice" + "github.com/bangumi/server/internal/pkg/null" + "github.com/bangumi/server/web/accessor" + "github.com/bangumi/server/web/req" + "github.com/bangumi/server/web/res" +) + +const defaultLimit = 10 +const maxLimit = 20 + +type Req struct { + Keyword string `json:"keyword"` + Filter ReqFilter `json:"filter"` +} + +type ReqFilter struct { //nolint:musttag + NSFW null.Bool `json:"nsfw"` +} + +type hit struct { + ID model.CharacterID `json:"id"` +} + +//nolint:funlen +func (c *client) Handle(ctx *echo.Context) error { + auth := accessor.GetFromCtx(ctx) + q, err := req.GetPageQuerySoftLimit(ctx, defaultLimit, maxLimit) + if err != nil { + return err + } + + var r Req + if err = json.NewDecoder(ctx.Request().Body).Decode(&r); err != nil { + return res.JSONError(ctx, err) + } + + if !auth.AllowNSFW() { + r.Filter.NSFW = null.Bool{Set: true, Value: false} + } + + result, err := c.doSearch(r.Keyword, filterToMeiliFilter(r.Filter), q.Limit, q.Offset) + if err != nil { + return errgo.Wrap(err, "search") + } + + var hits []hit + if err = json.Unmarshal(result.Hits, &hits); err != nil { + return errgo.Wrap(err, "json.Unmarshal") + } + ids := slice.Map(hits, func(h hit) model.SubjectID { return h.ID }) + + characters, err := c.repo.GetByIDs(ctx.Request().Context(), ids) + if err != nil { + return errgo.Wrap(err, "characterRepo.GetByIDs") + } + + var data = make([]res.CharacterV0, 0, len(characters)) + for _, id := range ids { + s, ok := characters[id] + if !ok { + continue + } + character := res.ConvertModelCharacter(s) + data = append(data, character) + } + + return ctx.JSON(http.StatusOK, res.Paged{ + Data: data, + Total: result.EstimatedTotalHits, + Limit: q.Limit, + Offset: q.Offset, + }) +} + +func (c *client) doSearch( + words string, + filter [][]string, + limit, offset int, +) (*meiliSearchResponse, error) { + if limit == 0 { + limit = 10 + } else if limit > 50 { + limit = 50 + } + + raw, err := c.index.SearchRaw(words, &meilisearch.SearchRequest{ + Offset: int64(offset), + Limit: int64(limit), + Filter: filter, + }) + if err != nil { + return nil, errgo.Wrap(err, "meilisearch search") + } + + var r meiliSearchResponse + if err := json.Unmarshal(*raw, &r); err != nil { + return nil, errgo.Wrap(err, "json.Unmarshal") + } + + return &r, nil +} + +type meiliSearchResponse struct { + Hits json.RawMessage `json:"hits"` + EstimatedTotalHits int64 `json:"estimatedTotalHits"` //nolint:tagliatelle +} + +func filterToMeiliFilter(req ReqFilter) [][]string { + var filter = make([][]string, 0, 1) + + if req.NSFW.Set { + filter = append(filter, []string{fmt.Sprintf("nsfw = %t", req.NSFW.Value)}) + } + + return filter +} diff --git a/internal/search/client.go b/internal/search/client.go deleted file mode 100644 index 9584d258d..000000000 --- a/internal/search/client.go +++ /dev/null @@ -1,320 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package search - -import ( - "context" - "errors" - "fmt" - "net/url" - "os" - "reflect" - "strconv" - "strings" - "time" - - "github.com/avast/retry-go/v4" - "github.com/meilisearch/meilisearch-go" - "github.com/prometheus/client_golang/prometheus" - "github.com/samber/lo" - "github.com/trim21/errgo" - "github.com/trim21/pkg/queue" - "go.uber.org/zap" - - "github.com/bangumi/server/config" - "github.com/bangumi/server/dal/query" - "github.com/bangumi/server/domain/gerr" - "github.com/bangumi/server/internal/model" - "github.com/bangumi/server/internal/subject" -) - -// New provide a search app is AppConfig.MeiliSearchURL is empty string, return nope search client. -// -// see `MeiliSearchURL` and `MeiliSearchKey` in [config.AppConfig]. -func New( - cfg config.AppConfig, - subjectRepo subject.Repo, - log *zap.Logger, - query *query.Query, -) (Client, error) { - if cfg.Search.MeiliSearch.URL == "" { - return NoopClient{}, nil - } - - if subjectRepo == nil { - panic("nil SubjectRepo") - } - if _, err := url.Parse(cfg.Search.MeiliSearch.URL); err != nil { - return nil, errgo.Wrap(err, "url.Parse") - } - - meili := meilisearch.NewClient(meilisearch.ClientConfig{ - Host: cfg.Search.MeiliSearch.URL, - APIKey: cfg.Search.MeiliSearch.Key, - Timeout: time.Second, - }) - - if _, err := meili.GetVersion(); err != nil { - return nil, errgo.Wrap(err, "meilisearch") - } - - c := &client{ - meili: meili, - q: query, - subject: "subjects", - subjectIndex: meili.Index("subjects"), - log: log.Named("search"), - subjectRepo: subjectRepo, - } - - if cfg.AppType != config.AppTypeCanal { - return c, nil - } - - return c, c.canalInit(cfg) -} - -func (c *client) canalInit(cfg config.AppConfig) error { - if cfg.Search.SearchBatchSize <= 0 { - // nolint: goerr113 - return fmt.Errorf("config.SearchBatchSize should >= 0, current %d", cfg.Search.SearchBatchSize) - } - - if cfg.Search.SearchBatchInterval <= 0 { - // nolint: goerr113 - return fmt.Errorf("config.SearchBatchInterval should >= 0, current %d", cfg.Search.SearchBatchInterval) - } - - c.queue = queue.NewBatchedDedupe[subjectIndex]( - c.sendBatch, - cfg.Search.SearchBatchSize, - cfg.Search.SearchBatchInterval, - func(items []subjectIndex) []subjectIndex { - // lo.UniqBy 会保留第一次出现的元素,reverse 之后会保留新的数据 - return lo.UniqBy(lo.Reverse(items), func(item subjectIndex) model.SubjectID { - return item.ID - }) - }, - ) - - prometheus.DefaultRegisterer.MustRegister( - prometheus.NewGaugeFunc( - prometheus.GaugeOpts{ - Namespace: "chii", - Name: "meilisearch_queue_batch", - Help: "meilisearch update queue batch size", - }, - func() float64 { - return float64(c.queue.Len()) - }, - )) - - shouldCreateIndex, err := c.needFirstRun() - if err != nil { - return err - } - - if shouldCreateIndex { - go c.firstRun() - } - - return nil -} - -type client struct { - subjectRepo subject.Repo - meili *meilisearch.Client - q *query.Query - subjectIndex *meilisearch.Index - log *zap.Logger - subject string - queue *queue.Batched[subjectIndex] -} - -func (c *client) Close() { - if c.queue != nil { - c.queue.Close() - } -} - -// OnSubjectUpdate is the hook called by canal. -func (c *client) OnSubjectUpdate(ctx context.Context, id model.SubjectID) error { - s, err := c.subjectRepo.Get(ctx, id, subject.Filter{}) - if err != nil { - if errors.Is(err, gerr.ErrNotFound) { - return c.DeleteSubject(ctx, id) - } - return errgo.Wrap(err, "subjectRepo.Get") - } - - extracted := extractSubject(&s) - - c.queue.Push(extracted) - - return nil -} - -// OnSubjectDelete is the hook called by canal. -func (c *client) OnSubjectDelete(_ context.Context, id model.SubjectID) error { - _, err := c.subjectIndex.DeleteDocument(strconv.FormatUint(uint64(id), 10)) - - return errgo.Wrap(err, "search") -} - -// UpsertSubject add subject to search backend. -func (c *client) sendBatch(items []subjectIndex) { - c.log.Debug("send batch to meilisearch", zap.Int("len", len(items))) - err := retry.Do( - func() error { - _, err := c.subjectIndex.UpdateDocuments(items, "id") - return err - }, - retry.OnRetry(func(n uint, err error) { - c.log.Warn("failed to send batch", zap.Uint("attempt", n), zap.Error(err)) - }), - - retry.DelayType(retry.BackOffDelay), - retry.Delay(time.Microsecond*100), - retry.Attempts(5), //nolint:gomnd - retry.RetryIf(func(err error) bool { - return errors.As(err, &meilisearch.Error{}) - }), - ) - - if err != nil { - c.log.Error("failed to send batch", zap.Error(err)) - } -} - -func (c *client) DeleteSubject(_ context.Context, id model.SubjectID) error { - _, err := c.subjectIndex.Delete(strconv.FormatUint(uint64(id), 10)) - - return errgo.Wrap(err, "delete") -} - -func (c *client) needFirstRun() (bool, error) { - if os.Getenv("CHII_SEARCH_INIT") == "true" { - return true, nil - } - - index, err := c.meili.GetIndex("subjects") - if err != nil { - var e *meilisearch.Error - if errors.As(err, &e) { - return true, nil - } - return false, errgo.Wrap(err, "get subjects index") - } - - stat, err := index.GetStats() - if err != nil { - return false, errgo.Wrap(err, "get subjects index stats") - } - - return stat.NumberOfDocuments == 0, nil -} - -//nolint:funlen -func (c *client) firstRun() { - c.log.Info("search initialize") - _, err := c.meili.CreateIndex(&meilisearch.IndexConfig{ - Uid: "subjects", - PrimaryKey: "id", - }) - if err != nil { - c.log.Fatal("failed to create search subject index", zap.Error(err)) - return - } - - subjectIndex := c.meili.Index("subjects") - - c.log.Info("set sortable attributes", zap.Strings("attributes", *getAttributes("sortable"))) - _, err = subjectIndex.UpdateSortableAttributes(getAttributes("sortable")) - if err != nil { - c.log.Fatal("failed to update search index sortable attributes", zap.Error(err)) - return - } - - c.log.Info("set filterable attributes", zap.Strings("attributes", *getAttributes("filterable"))) - _, err = subjectIndex.UpdateFilterableAttributes(getAttributes("filterable")) - if err != nil { - c.log.Fatal("failed to update search index filterable attributes", zap.Error(err)) - return - } - - c.log.Info("set searchable attributes", zap.Strings("attributes", *searchAbleAttribute())) - _, err = subjectIndex.UpdateSearchableAttributes(searchAbleAttribute()) - if err != nil { - c.log.Fatal("failed to update search index searchable attributes", zap.Error(err)) - return - } - - c.log.Info("set ranking rules", zap.Strings("rule", *rankRule())) - _, err = subjectIndex.UpdateRankingRules(rankRule()) - if err != nil { - c.log.Fatal("failed to update search index searchable attributes", zap.Error(err)) - return - } - - ctx := context.Background() - - maxSubject, err := c.q.Subject.WithContext(ctx).Limit(1).Order(c.q.Subject.ID.Desc()).Take() - if err != nil { - c.log.Fatal("failed to get current max subject id", zap.Error(err)) - return - } - - c.log.Info(fmt.Sprintf("run full search index with max subject id %d", maxSubject.ID)) - - width := len(strconv.Itoa(int(maxSubject.ID))) - for i := model.SubjectID(1); i < maxSubject.ID; i++ { - if i%10000 == 0 { - c.log.Info(fmt.Sprintf("progress %*d/%d", width, i, maxSubject.ID)) - } - - err := c.OnSubjectUpdate(ctx, i) - if err != nil { - c.log.Error("error when updating subject", zap.Error(err)) - } - } -} - -func getAttributes(tag string) *[]string { - rt := reflect.TypeOf(subjectIndex{}) - var s []string - for i := 0; i < rt.NumField(); i++ { - t, ok := rt.Field(i).Tag.Lookup(tag) - if !ok { - continue - } - - if t != "true" { - continue - } - - s = append(s, getJSONFieldName(rt.Field(i))) - } - - return &s -} - -func getJSONFieldName(f reflect.StructField) string { - t := f.Tag.Get("json") - if t == "" { - return f.Name - } - - return strings.Split(t, ",")[0] -} diff --git a/internal/search/extractor.common.go b/internal/search/extractor.common.go deleted file mode 100644 index 7f63e83e0..000000000 --- a/internal/search/extractor.common.go +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package search - -import ( - "github.com/bangumi/server/internal/model" - "github.com/bangumi/server/pkg/wiki" -) - -func heat(s *model.Subject) uint32 { - return s.OnHold + s.Doing + s.Dropped + s.Wish + s.Collect -} - -func extractNames(s *model.Subject, w wiki.Wiki) []string { - var names = make([]string, 0, 3) - names = append(names, s.Name) - if s.NameCN != "" { - names = append(names, s.NameCN) - } - - for _, field := range w.Fields { - if field.Key == "别名" { - names = append(names, getValues(field)...) - } - } - - return names -} - -func getValues(f wiki.Field) []string { - if f.Null { - return nil - } - - if !f.Array { - return []string{f.Value} - } - - var s = make([]string, len(f.Values)) - for i, value := range f.Values { - s[i] = value.Value - } - return s -} diff --git a/internal/search/handle.go b/internal/search/handle.go deleted file mode 100644 index 4404407a8..000000000 --- a/internal/search/handle.go +++ /dev/null @@ -1,303 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -// Package search 基于 meilisearch 提供搜索功能 -package search - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "strconv" - "strings" - - "github.com/labstack/echo/v4" - "github.com/meilisearch/meilisearch-go" - "github.com/trim21/errgo" - - "github.com/bangumi/server/internal/model" - "github.com/bangumi/server/internal/pkg/generic/slice" - "github.com/bangumi/server/internal/pkg/null" - "github.com/bangumi/server/internal/subject" - "github.com/bangumi/server/web/accessor" - "github.com/bangumi/server/web/req" - "github.com/bangumi/server/web/res" -) - -type Client interface { - Handler - OnSubjectUpdate(ctx context.Context, id model.SubjectID) error - Close() - OnSubjectDelete(ctx context.Context, id model.SubjectID) error -} - -// Handler -// TODO: 想个办法挪到 web 里面去. -type Handler interface { - Handle(c echo.Context) error -} - -const defaultLimit = 50 -const maxLimit = 200 - -type Req struct { - Keyword string `json:"keyword"` - Sort string `json:"sort"` - Filter ReqFilter `json:"filter"` -} - -type ReqFilter struct { //nolint:musttag - Type []model.SubjectType `json:"type"` // or - Tag []string `json:"tag"` // and - AirDate []string `json:"air_date"` // and - Score []string `json:"rating"` // and - Rank []string `json:"rank"` // and - NSFW null.Bool `json:"nsfw"` -} - -type hit struct { - ID model.SubjectID `json:"id"` -} - -type ReponseSubject struct { - Date string `json:"date"` - Image string `json:"image"` - Type uint8 `json:"type"` - Summary string `json:"summary"` - Name string `json:"name"` - NameCN string `json:"name_cn"` - Tags []res.SubjectTag `json:"tags"` - Score float64 `json:"score"` - ID model.SubjectID `json:"id"` - Rank uint32 `json:"rank"` -} - -func (c *client) Handle(ctx echo.Context) error { - auth := accessor.GetFromCtx(ctx) - q, err := req.GetPageQuery(ctx, defaultLimit, maxLimit) - if err != nil { - return err - } - - var r Req - if err = json.NewDecoder(ctx.Request().Body).Decode(&r); err != nil { - return res.JSONError(ctx, err) - } - - if !auth.AllowNSFW() { - r.Filter.NSFW = null.New(false) - } - - result, err := c.doSearch(r.Keyword, filterToMeiliFilter(r.Filter), r.Sort, q.Limit, q.Offset) - if err != nil { - return errgo.Wrap(err, "search") - } - - var hits []hit - if err = json.Unmarshal(result.Hits, &hits); err != nil { - return errgo.Wrap(err, "json.Unmarshal") - } - ids := slice.Map(hits, func(h hit) model.SubjectID { return h.ID }) - - subjects, err := c.subjectRepo.GetByIDs(ctx.Request().Context(), ids, subject.Filter{NSFW: r.Filter.NSFW}) - if err != nil { - return errgo.Wrap(err, "subjectRepo.GetByIDs") - } - - data := slice.Map(ids, func(id model.SubjectID) ReponseSubject { - s := subjects[id] - - return ReponseSubject{ - Date: s.Date, - Image: res.SubjectImage(s.Image).Large, - Type: s.TypeID, - Summary: s.Summary, - Name: s.Name, - NameCN: s.NameCN, - Tags: slice.Map(s.Tags, func(item model.Tag) res.SubjectTag { - return res.SubjectTag{Name: item.Name, Count: item.Count} - }), - Score: s.Rating.Score, - ID: s.ID, - Rank: s.Rating.Rank, - } - }) - - return ctx.JSON(http.StatusOK, res.Paged{ - Data: data, - Total: result.EstimatedTotalHits, - Limit: q.Limit, - Offset: q.Offset, - }) -} - -func (c *client) doSearch( - words string, - filter [][]string, - sort string, - limit, offset int, -) (*meiliSearchResponse, error) { - if limit == 0 { - limit = 10 - } else if limit > 50 { - limit = 50 - } - - var sortOpt []string - switch sort { - case "", "match": - case "score": - sortOpt = []string{"score:desc"} - case "heat": - sortOpt = []string{"heat:desc"} - case "rank": - sortOpt = []string{"rank:asc"} - default: - return nil, res.BadRequest("sort not supported") - } - - raw, err := c.subjectIndex.SearchRaw(words, &meilisearch.SearchRequest{ - Offset: int64(offset), - Limit: int64(limit), - Filter: filter, - Sort: sortOpt, - }) - if err != nil { - return nil, errgo.Wrap(err, "meilisearch search") - } - - var r meiliSearchResponse - if err := json.Unmarshal(*raw, &r); err != nil { - return nil, errgo.Wrap(err, "json.Unmarshal") - } - - return &r, nil -} - -type meiliSearchResponse struct { - Hits json.RawMessage `json:"hits"` - EstimatedTotalHits int64 `json:"estimatedTotalHits"` //nolint:tagliatelle -} - -func filterToMeiliFilter(req ReqFilter) [][]string { - var filter = make([][]string, 0, 5+len(req.Tag)) - - // OR - - if len(req.AirDate) != 0 { - filter = append(filter, parseDateFilter(req.AirDate)...) - } - - if len(req.Type) != 0 { - filter = append(filter, slice.Map(req.Type, func(s model.SubjectType) string { - return fmt.Sprintf("type = %d", s) - })) - } - if req.NSFW.Set { - filter = append(filter, []string{"nsfw = " + strconv.FormatBool(req.NSFW.Value)}) - } - - // AND - - for _, tag := range req.Tag { - filter = append(filter, []string{"tag = " + strconv.Quote(tag)}) - } - - for _, s := range req.Rank { - filter = append(filter, []string{"rank" + s}) - } - - for _, s := range req.Score { - filter = append(filter, []string{"score " + s}) - } - - return filter -} - -// parse date filter like `<2020-01-20`, `>=2020-01-23`. -func parseDateFilter(filters []string) [][]string { - var result = make([][]string, 0, len(filters)) - - for _, s := range filters { - switch { - case strings.HasPrefix(s, ">="): - if v, ok := parseDateValOk(s[2:]); ok { - result = append(result, []string{fmt.Sprintf("date >= %d", v)}) - } - case strings.HasPrefix(s, ">"): - if v, ok := parseDateValOk(s[1:]); ok { - result = append(result, []string{fmt.Sprintf("date > %d", v)}) - } - case strings.HasPrefix(s, "<="): - if v, ok := parseDateValOk(s[2:]); ok { - result = append(result, []string{fmt.Sprintf("date <= %d", v)}) - } - case strings.HasPrefix(s, "<"): - if v, ok := parseDateValOk(s[1:]); ok { - result = append(result, []string{fmt.Sprintf("date < %d", v)}) - } - default: - if v, ok := parseDateValOk(s); ok { - result = append(result, []string{fmt.Sprintf("date = %d", v)}) - } - } - } - - return result -} - -func parseDateValOk(date string) (int, bool) { - if len(date) < 10 { - return 0, false - } - - // 2008-10-05 format - if !(isDigitsOnly(date[:4]) && - date[4] == '-' && - isDigitsOnly(date[5:7]) && - date[7] == '-' && - isDigitsOnly(date[8:10])) { - return 0, false - } - - v, err := strconv.Atoi(date[:4]) - if err != nil { - return 0, false - } - val := v * 10000 - - v, err = strconv.Atoi(date[5:7]) - if err != nil { - return 0, false - } - val += v * 100 - - v, err = strconv.Atoi(date[8:10]) - if err != nil { - return 0, false - } - val += v - - return val, true -} - -func isDigitsOnly(s string) bool { - for _, c := range s { - if c < '0' || c > '9' { - return false - } - } - return true -} diff --git a/internal/search/index.go b/internal/search/index.go deleted file mode 100644 index a48641970..000000000 --- a/internal/search/index.go +++ /dev/null @@ -1,124 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package search - -import ( - "strconv" - - "github.com/bangumi/server/internal/model" - "github.com/bangumi/server/pkg/wiki" -) - -// 最终 meilisearch 索引的文档. -// 使用 `filterable:"true"`, `sortable:"true"` -// 两种 tag 来设置是否可以被索引和排序. -// 搜索字段因为带有排序,所以定义在 [search.searchAbleAttribute] 中. -type subjectIndex struct { - ID model.SubjectID `json:"id"` - Summary string `json:"summary"` - Tag []string `json:"tag,omitempty" filterable:"true"` - Name []string `json:"name"` - Date int `json:"date,omitempty" filterable:"true" sortable:"true"` - Score float64 `json:"score" filterable:"true" sortable:"true"` - PageRank float64 `json:"page_rank" sortable:"true"` - Heat uint32 `json:"heat" sortable:"true"` - Rank uint32 `json:"rank" filterable:"true" sortable:"true"` - Platform uint16 `json:"platform,omitempty"` - Type uint8 `json:"type" filterable:"true"` - NSFW bool `json:"nsfw" filterable:"true"` -} - -func searchAbleAttribute() *[]string { - return &[]string{ - "name", - "summary", - "tag", - "type", - "id", - } -} - -func rankRule() *[]string { - return &[]string{ - // 相似度最优先 - "exactness", - "words", - "typo", - "proximity", - "attribute", - "sort", - // id 在前的优先展示,主要是为了系列作品能有个很好的顺序 - "id:asc", - // 以下酌情,我选择优先展示排行榜排名更高、评分更高的条目,且尽量优先展示 sfw 内容 - "rank:asc", - "score:desc", - "nsfw:asc", - } -} - -func extractSubject(s *model.Subject) subjectIndex { - tags := s.Tags - - w := wiki.ParseOmitError(s.Infobox) - - score := s.Rating.Score - - tagNames := make([]string, len(tags)) - for i, tag := range tags { - tagNames[i] = tag.Name - } - - return subjectIndex{ - ID: s.ID, - Name: extractNames(s, w), - Tag: tagNames, - Summary: s.Summary, - NSFW: s.NSFW, - Type: s.TypeID, - Date: parseDateVal(s.Date), - Platform: s.PlatformID, - PageRank: float64(s.Rating.Total), - Rank: s.Rating.Rank, - Heat: heat(s), - Score: score, - } -} - -func parseDateVal(date string) int { - if len(date) < 10 { - return 0 - } - - // 2008-10-05 format - v, err := strconv.Atoi(date[:4]) - if err != nil { - return 0 - } - val := v * 10000 - - v, err = strconv.Atoi(date[5:7]) - if err != nil { - return 0 - } - val += v * 100 - - v, err = strconv.Atoi(date[8:10]) - if err != nil { - return 0 - } - val += v - - return val -} diff --git a/internal/search/noop.go b/internal/search/noop.go index 0a929174b..b794201d2 100644 --- a/internal/search/noop.go +++ b/internal/search/noop.go @@ -18,9 +18,7 @@ import ( "context" "net/http" - "github.com/labstack/echo/v4" - - "github.com/bangumi/server/internal/model" + "github.com/labstack/echo/v5" ) var _ Client = NoopClient{} @@ -28,15 +26,19 @@ var _ Client = NoopClient{} type NoopClient struct { } -func (n NoopClient) Handle(c echo.Context) error { +func (n NoopClient) Handle(c *echo.Context, _ SearchTarget) error { return c.String(http.StatusOK, "search is not enable") } -func (n NoopClient) OnSubjectUpdate(_ context.Context, _ model.SubjectID) error { +func (n NoopClient) EventAdded(ctx context.Context, _ uint32, _ SearchTarget) error { + return nil +} + +func (n NoopClient) EventUpdate(_ context.Context, _ uint32, _ SearchTarget) error { return nil } -func (n NoopClient) OnSubjectDelete(_ context.Context, _ model.SubjectID) error { +func (n NoopClient) EventDelete(_ context.Context, _ uint32, _ SearchTarget) error { return nil } diff --git a/internal/search/person/client.go b/internal/search/person/client.go new file mode 100644 index 000000000..46c8fa7df --- /dev/null +++ b/internal/search/person/client.go @@ -0,0 +1,99 @@ +package person + +import ( + "context" + "fmt" + "reflect" + "strconv" + + "github.com/meilisearch/meilisearch-go" + "github.com/trim21/errgo" + "go.uber.org/zap" + + "github.com/bangumi/server/config" + "github.com/bangumi/server/dal/query" + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/person" + "github.com/bangumi/server/internal/search/searcher" +) + +const ( + idx = "persons" +) + +func New( + cfg config.AppConfig, + meili meilisearch.ServiceManager, + repo person.Repo, + log *zap.Logger, + query *query.Query, +) (searcher.Searcher, error) { + if repo == nil { + return nil, fmt.Errorf("nil personRepo") + } + c := &client{ + meili: meili, + repo: repo, + index: meili.Index("persons"), + log: log.Named("search").With(zap.String("index", idx)), + q: query, + } + + if cfg.AppType != config.AppTypeCanal { + return c, nil + } + + return c, c.canalInit(cfg) +} + +type client struct { + repo person.Repo + index meilisearch.IndexManager + + meili meilisearch.ServiceManager + log *zap.Logger + q *query.Query +} + +func (c *client) canalInit(cfg config.AppConfig) error { + if err := searcher.ValidateConfigs(cfg); err != nil { + return errgo.Wrap(err, "validate search config") + } + shouldCreateIndex, err := searcher.NeedFirstRun(c.meili, idx) + if err != nil { + return err + } + if shouldCreateIndex { + go c.firstRun() + } + return nil +} + +//nolint:funlen +func (c *client) firstRun() { + c.log.Info("search initialize") + rt := reflect.TypeOf(document{}) + searcher.InitIndex(c.log, c.meili, idx, rt, rankRule()) + + ctx := context.Background() + + maxItem, err := c.q.Person.WithContext(ctx).Limit(1).Order(c.q.Person.ID.Desc()).Take() + if err != nil { + c.log.Fatal("failed to get current max id", zap.Error(err)) + return + } + + c.log.Info(fmt.Sprintf("run full search index with max %s id %d", idx, maxItem.ID)) + + width := len(strconv.Itoa(int(maxItem.ID))) + for i := model.PersonID(1); i <= maxItem.ID; i++ { + if i%10000 == 0 { + c.log.Info(fmt.Sprintf("progress %*d/%d", width, i, maxItem.ID)) + } + + err := c.OnUpdate(ctx, i) + if err != nil { + c.log.Error("error when updating", zap.Error(err)) + } + } +} diff --git a/internal/search/person/doc.go b/internal/search/person/doc.go new file mode 100644 index 000000000..6be3d8cce --- /dev/null +++ b/internal/search/person/doc.go @@ -0,0 +1,51 @@ +package person + +import ( + "strconv" + + wiki "github.com/bangumi/wiki-parser-go" + + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/search/searcher" +) + +type document struct { + ID model.PersonID `json:"id"` + Name string `json:"name" searchable:"true"` + Aliases []string `json:"aliases,omitempty" searchable:"true"` + Comment uint32 `json:"comment" sortable:"true"` + Collect uint32 `json:"collect" sortable:"true"` + Career []string `json:"career,omitempty" filterable:"true"` +} + +func (d *document) GetID() string { + return strconv.FormatUint(uint64(d.ID), 10) +} + +func rankRule() *[]string { + return &[]string{ + // 相似度最优先 + "exactness", + "words", + "typo", + "proximity", + "attribute", + "sort", + "id:asc", + "comment:desc", + "collect:desc", + } +} + +func extract(c *model.Person) searcher.Document { + w := wiki.ParseOmitError(c.Infobox) + + return &document{ + ID: c.ID, + Name: c.Name, + Aliases: searcher.ExtractAliases(w), + Comment: c.CommentCount, + Collect: c.CollectCount, + Career: c.Careers(), + } +} diff --git a/internal/search/person/event.go b/internal/search/person/event.go new file mode 100644 index 000000000..eeb132396 --- /dev/null +++ b/internal/search/person/event.go @@ -0,0 +1,59 @@ +package person + +import ( + "context" + "errors" + "strconv" + + "github.com/meilisearch/meilisearch-go" + "github.com/samber/lo" + "github.com/trim21/errgo" + + "github.com/bangumi/server/domain/gerr" + "github.com/bangumi/server/internal/model" +) + +func (c *client) OnAdded(ctx context.Context, id model.PersonID) error { + s, err := c.repo.Get(ctx, id) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return nil + } + return errgo.Wrap(err, "characterRepo.Get") + } + + if s.Redirect != 0 { + return c.OnDelete(ctx, id) + } + + extracted := extract(&s) + + _, err = c.index.UpdateDocumentsWithContext(ctx, extracted, &meilisearch.DocumentOptions{PrimaryKey: lo.ToPtr("id")}) + return err +} + +func (c *client) OnUpdate(ctx context.Context, id model.PersonID) error { + s, err := c.repo.Get(ctx, id) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return nil + } + return errgo.Wrap(err, "characterRepo.Get") + } + + if s.Redirect != 0 { + return c.OnDelete(ctx, id) + } + + extracted := extract(&s) + + _, err = c.index.UpdateDocumentsWithContext(ctx, extracted, &meilisearch.DocumentOptions{PrimaryKey: lo.ToPtr("id")}) + + return err +} + +func (c *client) OnDelete(ctx context.Context, id model.PersonID) error { + _, err := c.index.DeleteDocumentWithContext(ctx, strconv.FormatUint(uint64(id), 10), nil) + + return errgo.Wrap(err, "search") +} diff --git a/internal/search/person/handle.go b/internal/search/person/handle.go new file mode 100644 index 000000000..f313c3183 --- /dev/null +++ b/internal/search/person/handle.go @@ -0,0 +1,121 @@ +package person + +import ( + "encoding/json" + "net/http" + "strconv" + + "github.com/labstack/echo/v5" + "github.com/meilisearch/meilisearch-go" + "github.com/trim21/errgo" + + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/pkg/generic/slice" + "github.com/bangumi/server/web/req" + "github.com/bangumi/server/web/res" +) + +const defaultLimit = 10 +const maxLimit = 20 + +type Req struct { + Keyword string `json:"keyword"` + Filter ReqFilter `json:"filter"` +} + +type ReqFilter struct { //nolint:musttag + Careers []string `json:"career"` // and +} + +type hit struct { + ID model.PersonID `json:"id"` +} + +//nolint:funlen +func (c *client) Handle(ctx *echo.Context) error { + q, err := req.GetPageQuerySoftLimit(ctx, defaultLimit, maxLimit) + if err != nil { + return err + } + + var r Req + if err = json.NewDecoder(ctx.Request().Body).Decode(&r); err != nil { + return res.JSONError(ctx, err) + } + + result, err := c.doSearch(r.Keyword, filterToMeiliFilter(r.Filter), q.Limit, q.Offset) + if err != nil { + return errgo.Wrap(err, "search") + } + + var hits []hit + if err = json.Unmarshal(result.Hits, &hits); err != nil { + return errgo.Wrap(err, "json.Unmarshal") + } + ids := slice.Map(hits, func(h hit) model.SubjectID { return h.ID }) + + persons, err := c.repo.GetByIDs(ctx.Request().Context(), ids) + if err != nil { + return errgo.Wrap(err, "personRepo.GetByIDs") + } + + var data = make([]res.PersonV0, 0, len(persons)) + for _, id := range ids { + s, ok := persons[id] + if !ok { + continue + } + person := res.ConvertModelPerson(s) + data = append(data, person) + } + + return ctx.JSON(http.StatusOK, res.Paged{ + Data: data, + Total: result.EstimatedTotalHits, + Limit: q.Limit, + Offset: q.Offset, + }) +} + +func (c *client) doSearch( + words string, + filter [][]string, + limit, offset int, +) (*meiliSearchResponse, error) { + if limit == 0 { + limit = 10 + } else if limit > 50 { + limit = 50 + } + + raw, err := c.index.SearchRaw(words, &meilisearch.SearchRequest{ + Offset: int64(offset), + Limit: int64(limit), + Filter: filter, + }) + if err != nil { + return nil, errgo.Wrap(err, "meilisearch search") + } + + var r meiliSearchResponse + if err := json.Unmarshal(*raw, &r); err != nil { + return nil, errgo.Wrap(err, "json.Unmarshal") + } + + return &r, nil +} + +type meiliSearchResponse struct { + Hits json.RawMessage `json:"hits"` + EstimatedTotalHits int64 `json:"estimatedTotalHits"` //nolint:tagliatelle +} + +func filterToMeiliFilter(req ReqFilter) [][]string { + var filter = make([][]string, 0, len(req.Careers)) + + for _, career := range req.Careers { + filter = append(filter, []string{"career = " + strconv.Quote(career)}) + } + + return filter +} diff --git a/internal/search/search.go b/internal/search/search.go new file mode 100644 index 000000000..8d2e8a859 --- /dev/null +++ b/internal/search/search.go @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +package search + +import ( + "context" + "fmt" + "net/http" + "net/url" + + "github.com/labstack/echo/v5" + "github.com/meilisearch/meilisearch-go" + "github.com/trim21/errgo" + "go.uber.org/zap" + + "github.com/bangumi/server/config" + "github.com/bangumi/server/dal/query" + "github.com/bangumi/server/internal/character" + "github.com/bangumi/server/internal/person" + characterSearcher "github.com/bangumi/server/internal/search/character" + personSearcher "github.com/bangumi/server/internal/search/person" + "github.com/bangumi/server/internal/search/searcher" + subjectSearcher "github.com/bangumi/server/internal/search/subject" + "github.com/bangumi/server/internal/subject" +) + +type SearchTarget string + +const ( + SearchTargetSubject SearchTarget = "subject" + SearchTargetCharacter SearchTarget = "character" + SearchTargetPerson SearchTarget = "person" +) + +type Client interface { + Handle(c *echo.Context, target SearchTarget) error + Close() + + EventAdded(ctx context.Context, id uint32, target SearchTarget) error + EventUpdate(ctx context.Context, id uint32, target SearchTarget) error + EventDelete(ctx context.Context, id uint32, target SearchTarget) error +} + +type Handler interface { + Handle(c *echo.Context, target SearchTarget) error +} + +type Search struct { + searchers map[SearchTarget]searcher.Searcher +} + +// New provide a search app is AppConfig.MeiliSearchURL is empty string, return nope search client. +// +// see `MeiliSearchURL` and `MeiliSearchKey` in [config.AppConfig]. +func New( + cfg config.AppConfig, + subjectRepo subject.Repo, + characterRepo character.Repo, + personRepo person.Repo, + log *zap.Logger, + query *query.Query, +) (Client, error) { + if cfg.Search.MeiliSearch.URL == "" { + return NoopClient{}, nil + } + if _, err := url.Parse(cfg.Search.MeiliSearch.URL); err != nil { + return nil, errgo.Wrap(err, "url.Parse") + } + meili := meilisearch.New( + cfg.Search.MeiliSearch.URL, + meilisearch.WithAPIKey(cfg.Search.MeiliSearch.Key), + meilisearch.WithCustomClient(&http.Client{Timeout: cfg.Search.MeiliSearch.Timeout}), + ) + if _, err := meili.Version(); err != nil { + return nil, errgo.Wrap(err, "meilisearch") + } + + subject, err := subjectSearcher.New(cfg, meili, subjectRepo, log, query) + if err != nil { + return nil, errgo.Wrap(err, "subject search") + } + character, err := characterSearcher.New(cfg, meili, characterRepo, log, query) + if err != nil { + return nil, errgo.Wrap(err, "character search") + } + person, err := personSearcher.New(cfg, meili, personRepo, log, query) + if err != nil { + return nil, errgo.Wrap(err, "person search") + } + + searchers := map[SearchTarget]searcher.Searcher{ + SearchTargetSubject: subject, + SearchTargetCharacter: character, + SearchTargetPerson: person, + } + s := &Search{ + searchers: searchers, + } + return s, nil +} + +func (s *Search) Handle(c *echo.Context, target SearchTarget) error { + searcher := s.searchers[target] + if searcher == nil { + return fmt.Errorf("searcher not found for %s", target) + } + return searcher.Handle(c) +} + +func (s *Search) EventAdded(ctx context.Context, id uint32, target SearchTarget) error { + searcher := s.searchers[target] + if searcher == nil { + return fmt.Errorf("searcher not found for %s", target) + } + return searcher.OnAdded(ctx, id) +} + +func (s *Search) EventUpdate(ctx context.Context, id uint32, target SearchTarget) error { + searcher := s.searchers[target] + if searcher == nil { + return fmt.Errorf("searcher not found for %s", target) + } + return searcher.OnUpdate(ctx, id) +} + +func (s *Search) EventDelete(ctx context.Context, id uint32, target SearchTarget) error { + searcher := s.searchers[target] + if searcher == nil { + return fmt.Errorf("searcher not found for %s", target) + } + return searcher.OnDelete(ctx, id) +} + +func (s *Search) Close() {} diff --git a/internal/search/searcher/client.go b/internal/search/searcher/client.go new file mode 100644 index 000000000..0b132e1b0 --- /dev/null +++ b/internal/search/searcher/client.go @@ -0,0 +1,196 @@ +package searcher + +import ( + "context" + "errors" + "fmt" + "os" + "reflect" + "strings" + "time" + + "github.com/avast/retry-go/v5" + wiki "github.com/bangumi/wiki-parser-go" + "github.com/labstack/echo/v5" + "github.com/meilisearch/meilisearch-go" + "github.com/samber/lo" + "github.com/samber/lo/mutable" + "github.com/trim21/errgo" + "go.uber.org/zap" + + "github.com/bangumi/server/config" +) + +type Searcher interface { + Handle(c *echo.Context) error + + OnAdded(ctx context.Context, id uint32) error + OnUpdate(ctx context.Context, id uint32) error + OnDelete(ctx context.Context, id uint32) error +} + +type Document interface { + GetID() string +} + +func NeedFirstRun(meili meilisearch.ServiceManager, idx string) (bool, error) { + if os.Getenv("CHII_SEARCH_INIT") == "true" { + return true, nil + } + + index, err := meili.GetIndex(idx) + if err != nil { + var e *meilisearch.Error + if errors.As(err, &e) { + return true, nil + } + return false, errgo.Wrap(err, fmt.Sprintf("get index %s", idx)) + } + + stat, err := index.GetStats() + if err != nil { + return false, errgo.Wrap(err, fmt.Sprintf("get index %s stats", idx)) + } + + return stat.NumberOfDocuments == 0, nil +} + +func ValidateConfigs(cfg config.AppConfig) error { + return nil +} + +func ExtractAliases(w wiki.Wiki) []string { + aliases := []string{} + for _, field := range w.Fields { + if field.Key == "中文名" { + aliases = append(aliases, GetWikiValues(field)...) + } + if field.Key == "简体中文名" { + aliases = append(aliases, GetWikiValues(field)...) + } + } + for _, field := range w.Fields { + if field.Key == "别名" { + aliases = append(aliases, GetWikiValues(field)...) + } + } + return aliases +} + +func GetWikiValues(f wiki.Field) []string { + if f.Null { + return nil + } + + if !f.Array { + return []string{f.Value} + } + + var s = make([]string, len(f.Values)) + for i, value := range f.Values { + s[i] = value.Value + } + return s +} + +func NewSendBatch(log *zap.Logger, index meilisearch.IndexManager) func([]Document) { + var retrier = retry.New( + retry.OnRetry(func(n uint, err error) { + log.Warn("failed to send batch", zap.Uint("attempt", n), zap.Error(err)) + }), + retry.DelayType(retry.BackOffDelay), + retry.Delay(time.Second), + retry.Attempts(5), //nolint:mnd + retry.RetryIf(func(err error) bool { + var r = &meilisearch.Error{} + return errors.As(err, &r) + }), + ) + + return func(items []Document) { + log.Debug("send batch to meilisearch", zap.Int("len", len(items))) + err := retrier.Do(func() error { + _, err := index.UpdateDocuments(items, &meilisearch.DocumentOptions{PrimaryKey: lo.ToPtr("id")}) + return err + }) + if err != nil { + log.Error("failed to send batch", zap.Error(err)) + } + } +} + +func NewDedupeFunc() func([]Document) []Document { + return func(items []Document) []Document { + mutable.Reverse(items) + return lo.UniqBy(items, func(item Document) string { + return item.GetID() + }) + } +} + +func GetAttributes(rt reflect.Type, tag string) *[]string { + var s []string + for i := 0; i < rt.NumField(); i++ { + t, ok := rt.Field(i).Tag.Lookup(tag) + if !ok { + continue + } + if t != "true" { + continue + } + s = append(s, getJSONFieldName(rt.Field(i))) + } + return &s +} + +func getJSONFieldName(f reflect.StructField) string { + t := f.Tag.Get("json") + if t == "" { + return f.Name + } + return strings.Split(t, ",")[0] +} + +func InitIndex(log *zap.Logger, meili meilisearch.ServiceManager, idx string, rt reflect.Type, rankRule *[]string) { + _, err := meili.CreateIndex(&meilisearch.IndexConfig{ + Uid: idx, + PrimaryKey: "id", + }) + if err != nil { + log.Fatal("failed to create search index", zap.Error(err)) + return + } + + index := meili.Index(idx) + + log.Info("set sortable attributes", zap.Strings("attributes", *GetAttributes(rt, "sortable"))) + _, err = index.UpdateSortableAttributes(GetAttributes(rt, "sortable")) + if err != nil { + log.Fatal("failed to update search index sortable attributes", zap.Error(err)) + return + } + + log.Info("set filterable attributes", zap.Strings("attributes", *GetAttributes(rt, "filterable"))) + _, err = index.UpdateFilterableAttributes(lo.ToPtr( + lo.Map(*GetAttributes(rt, "filterable"), func(s string, index int) any { + return s + }))) + if err != nil { + log.Fatal("failed to update search index filterable attributes", zap.Error(err)) + return + } + + log.Info("set searchable attributes", zap.Strings("attributes", *GetAttributes(rt, "searchable"))) + _, err = index.UpdateSearchableAttributes(GetAttributes(rt, "searchable")) + if err != nil { + log.Fatal("failed to update search index searchable attributes", zap.Error(err)) + return + } + + log.Info("set ranking rules", zap.Strings("rule", *rankRule)) + _, err = index.UpdateRankingRules(rankRule) + if err != nil { + log.Fatal("failed to update search index searchable attributes", zap.Error(err)) + return + } +} diff --git a/internal/search/subject/client.go b/internal/search/subject/client.go new file mode 100644 index 000000000..6c19a7a71 --- /dev/null +++ b/internal/search/subject/client.go @@ -0,0 +1,99 @@ +package subject + +import ( + "context" + "fmt" + "reflect" + "strconv" + + "github.com/meilisearch/meilisearch-go" + "github.com/trim21/errgo" + "go.uber.org/zap" + + "github.com/bangumi/server/config" + "github.com/bangumi/server/dal/query" + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/search/searcher" + "github.com/bangumi/server/internal/subject" +) + +const ( + idx = "subjects" +) + +func New( + cfg config.AppConfig, + meili meilisearch.ServiceManager, + repo subject.Repo, + log *zap.Logger, + query *query.Query, +) (searcher.Searcher, error) { + if repo == nil { + return nil, fmt.Errorf("nil subjectRepo") + } + c := &client{ + meili: meili, + repo: repo, + index: meili.Index(idx), + log: log.Named("search").With(zap.String("index", idx)), + q: query, + } + + if cfg.AppType != config.AppTypeCanal { + return c, nil + } + + return c, c.canalInit(cfg) +} + +type client struct { + repo subject.Repo + index meilisearch.IndexManager + + meili meilisearch.ServiceManager + log *zap.Logger + q *query.Query +} + +func (c *client) canalInit(cfg config.AppConfig) error { + if err := searcher.ValidateConfigs(cfg); err != nil { + return errgo.Wrap(err, "validate search config") + } + shouldCreateIndex, err := searcher.NeedFirstRun(c.meili, idx) + if err != nil { + return err + } + if shouldCreateIndex { + go c.firstRun() + } + return nil +} + +//nolint:funlen +func (c *client) firstRun() { + c.log.Info("search initialize") + rt := reflect.TypeOf(document{}) + searcher.InitIndex(c.log, c.meili, idx, rt, rankRule()) + + ctx := context.Background() + + maxItem, err := c.q.Subject.WithContext(ctx).Limit(1).Order(c.q.Subject.ID.Desc()).Take() + if err != nil { + c.log.Fatal("failed to get current max id", zap.Error(err)) + return + } + + c.log.Info(fmt.Sprintf("run full search index with max %s id %d", idx, maxItem.ID)) + + width := len(strconv.Itoa(int(maxItem.ID))) + for i := model.SubjectID(1); i <= maxItem.ID; i++ { + if i%10000 == 0 { + c.log.Info(fmt.Sprintf("progress %*d/%d", width, i, maxItem.ID)) + } + + err := c.OnUpdate(ctx, i) + if err != nil { + c.log.Error("error when updating", zap.Error(err)) + } + } +} diff --git a/internal/search/subject/doc.go b/internal/search/subject/doc.go new file mode 100644 index 000000000..7455d4c9c --- /dev/null +++ b/internal/search/subject/doc.go @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +package subject + +import ( + "strconv" + "strings" + + wiki "github.com/bangumi/wiki-parser-go" + + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/search/searcher" +) + +// 最终 meilisearch 索引的文档. +// 使用 `filterable:"true"`, `sortable:"true"` +// 两种 tag 来设置是否可以被索引和排序. +// 搜索字段因为带有排序,所以定义在 [search.searchAbleAttribute] 中. +type document struct { + ID model.SubjectID `json:"id"` + Tag []string `json:"tag,omitempty" filterable:"true"` + MetaTags []string `json:"meta_tag" filterable:"true"` + Name string `json:"name" searchable:"true"` + Aliases []string `json:"aliases,omitempty" searchable:"true"` + Date int `json:"date,omitempty" filterable:"true" sortable:"true"` + Score float64 `json:"score" filterable:"true" sortable:"true"` + RatingCount uint32 `json:"rating_count" filterable:"true" sortable:"true"` + PageRank float64 `json:"page_rank" sortable:"true"` + Heat uint32 `json:"heat" sortable:"true"` + Rank uint32 `json:"rank" filterable:"true" sortable:"true"` + Platform uint16 `json:"platform,omitempty"` + Type uint8 `json:"type" filterable:"true"` + NSFW bool `json:"nsfw" filterable:"true"` +} + +func (d *document) GetID() string { + return strconv.FormatUint(uint64(d.ID), 10) +} + +func rankRule() *[]string { + return &[]string{ + // 相似度最优先 + "exactness", + "words", + "typo", + "proximity", + "attribute", + "sort", + // id 在前的优先展示,主要是为了系列作品能有个很好的顺序 + "id:asc", + // 以下酌情,我选择优先展示排行榜排名更高、评分更高的条目,且尽量优先展示 sfw 内容 + "rank:asc", + "score:desc", + "nsfw:asc", + } +} + +func heat(s *model.Subject) uint32 { + return s.OnHold + s.Doing + s.Dropped + s.Wish + s.Collect +} + +func extract(s *model.Subject) searcher.Document { + tags := s.Tags + + w := wiki.ParseOmitError(s.Infobox) + + score := s.Rating.Score + + tagNames := make([]string, len(tags)) + for i, tag := range tags { + tagNames[i] = tag.Name + } + + return &document{ + ID: s.ID, + Name: s.Name, + Aliases: extractAliases(s, w), + MetaTags: strings.Split(s.MetaTags, " "), + Tag: tagNames, + NSFW: s.NSFW, + Type: s.TypeID, + Date: parseDateVal(s.Date), + Platform: s.PlatformID, + RatingCount: s.Rating.Total, + PageRank: float64(s.Rating.Total), + Rank: s.Rating.Rank, + Heat: heat(s), + Score: score, + } +} + +func extractAliases(s *model.Subject, w wiki.Wiki) []string { + var aliases = make([]string, 0, 2) + if s.NameCN != "" { + aliases = append(aliases, s.NameCN) + } + + for _, field := range w.Fields { + if field.Key == "别名" { + aliases = append(aliases, searcher.GetWikiValues(field)...) + } + } + + return aliases +} + +func parseDateVal(date string) int { + if len(date) < 10 { + return 0 + } + + // 2008-10-05 format + v, err := strconv.Atoi(date[:4]) + if err != nil { + return 0 + } + val := v * 10000 + + v, err = strconv.Atoi(date[5:7]) + if err != nil { + return 0 + } + val += v * 100 + + v, err = strconv.Atoi(date[8:10]) + if err != nil { + return 0 + } + val += v + + return val +} diff --git a/internal/search/extract_internal_test.go b/internal/search/subject/doc_internal_test.go similarity index 98% rename from internal/search/extract_internal_test.go rename to internal/search/subject/doc_internal_test.go index f84874568..6af854169 100644 --- a/internal/search/extract_internal_test.go +++ b/internal/search/subject/doc_internal_test.go @@ -12,7 +12,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see -package search +package subject import ( "testing" diff --git a/internal/search/subject/event.go b/internal/search/subject/event.go new file mode 100644 index 000000000..3bfdfeb1d --- /dev/null +++ b/internal/search/subject/event.go @@ -0,0 +1,60 @@ +package subject + +import ( + "context" + "errors" + "strconv" + + "github.com/meilisearch/meilisearch-go" + "github.com/samber/lo" + "github.com/trim21/errgo" + + "github.com/bangumi/server/domain/gerr" + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/subject" +) + +func (c *client) OnAdded(ctx context.Context, id model.SubjectID) error { + s, err := c.repo.Get(ctx, id, subject.Filter{}) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return nil + } + return errgo.Wrap(err, "subjectRepo.Get") + } + + if s.Redirect != 0 || s.Ban != 0 { + return c.OnDelete(ctx, id) + } + + extracted := extract(&s) + + _, err = c.index.UpdateDocumentsWithContext(ctx, extracted, &meilisearch.DocumentOptions{PrimaryKey: lo.ToPtr("id")}) + return err +} + +func (c *client) OnUpdate(ctx context.Context, id model.SubjectID) error { + s, err := c.repo.Get(ctx, id, subject.Filter{}) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return nil + } + return errgo.Wrap(err, "subjectRepo.Get") + } + + if s.Redirect != 0 || s.Ban != 0 { + return c.OnDelete(ctx, id) + } + + extracted := extract(&s) + + _, err = c.index.UpdateDocumentsWithContext(ctx, extracted, &meilisearch.DocumentOptions{PrimaryKey: lo.ToPtr("id")}) + + return err +} + +func (c *client) OnDelete(ctx context.Context, id model.SubjectID) error { + _, err := c.index.DeleteDocumentWithContext(ctx, strconv.FormatUint(uint64(id), 10), nil) + + return errgo.Wrap(err, "search") +} diff --git a/internal/search/subject/handle.go b/internal/search/subject/handle.go new file mode 100644 index 000000000..2af4b5a4e --- /dev/null +++ b/internal/search/subject/handle.go @@ -0,0 +1,415 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +// Package subject 提供 subject 相关的搜索功能 +package subject + +import ( + "encoding/json" + "fmt" + "net/http" + "regexp" + "strconv" + "strings" + + "github.com/bangumi/wiki-parser-go" + "github.com/labstack/echo/v5" + "github.com/meilisearch/meilisearch-go" + "github.com/samber/lo" + "github.com/trim21/errgo" + + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/pkg/compat" + "github.com/bangumi/server/internal/pkg/generic/slice" + "github.com/bangumi/server/internal/pkg/null" + "github.com/bangumi/server/internal/subject" + "github.com/bangumi/server/internal/tag" + "github.com/bangumi/server/web/accessor" + "github.com/bangumi/server/web/req" + "github.com/bangumi/server/web/res" +) + +const defaultLimit = 10 +const maxLimit = 20 + +type Req struct { + Keyword string `json:"keyword"` + Sort string `json:"sort"` + Filter ReqFilter `json:"filter"` +} + +type ReqFilter struct { //nolint:musttag + Type []model.SubjectType `json:"type"` // or + Tag []string `json:"tag"` // and + AirDate []string `json:"air_date"` // and + Score []string `json:"rating"` // and + RatingCount []string `json:"rating_count"` // and + Rank []string `json:"rank"` // and + MetaTags []string `json:"meta_tags"` // and + + // if NSFW subject is enabled + NSFW null.Bool `json:"nsfw"` +} + +type hit struct { + ID model.SubjectID `json:"id"` +} + +type ResponseSubject struct { + Date *string `json:"date"` + Platform *string `json:"platform"` + Images res.SubjectImages `json:"images"` + Image string `json:"image"` + Summary string `json:"summary"` + Name string `json:"name"` + NameCN string `json:"name_cn"` + Tags []res.SubjectTag `json:"tags"` + Infobox res.V0wiki `json:"infobox"` + Rating res.Rating `json:"rating"` + Collection res.SubjectCollectionStat `json:"collection"` + ID model.SubjectID `json:"id"` + Eps uint32 `json:"eps"` + TotalEpisodes int64 `json:"total_episodes"` + MetaTags []string `json:"meta_tags"` + Volumes uint32 `json:"volumes"` + Series bool `json:"series"` + Locked bool `json:"locked"` + NSFW bool `json:"nsfw"` + TypeID model.SubjectType `json:"type"` + Redirect model.SubjectID `json:"-"` +} + +//nolint:funlen +func (c *client) Handle(ctx *echo.Context) error { + auth := accessor.GetFromCtx(ctx) + q, err := req.GetPageQuerySoftLimit(ctx, defaultLimit, maxLimit) + if err != nil { + return err + } + + var r Req + if err = json.NewDecoder(ctx.Request().Body).Decode(&r); err != nil { + return res.JSONError(ctx, err) + } + + if !auth.AllowNSFW() { + r.Filter.NSFW = null.Bool{Set: true, Value: false} + } + + meiliFilter, err := filterToMeiliFilter(r.Filter) + if err != nil { + return err + } + + result, err := c.doSearch(r.Keyword, meiliFilter, r.Sort, q.Limit, q.Offset) + if err != nil { + return errgo.Wrap(err, "search") + } + + var hits []hit + if err = json.Unmarshal(result.Hits, &hits); err != nil { + return errgo.Wrap(err, "json.Unmarshal") + } + ids := slice.Map(hits, func(h hit) model.SubjectID { return h.ID }) + + subjects, err := c.repo.GetByIDs(ctx.Request().Context(), ids, subject.Filter{}) + if err != nil { + return errgo.Wrap(err, "subjectRepo.GetByIDs") + } + + var data = make([]ResponseSubject, 0, len(subjects)) + for _, id := range ids { + s, ok := subjects[id] + if !ok { + continue + } + var metaTags []tag.Tag + + for _, t := range strings.Split(s.MetaTags, " ") { + if t == "" { + continue + } + metaTags = append(metaTags, tag.Tag{Name: t, Count: 1}) + } + + data = append(data, toResponseSubject(s, metaTags)) + } + + return ctx.JSON(http.StatusOK, res.Paged{ + Data: data, + Total: result.EstimatedTotalHits, + Limit: q.Limit, + Offset: q.Offset, + }) +} + +var intFilterPattern = regexp.MustCompile(`^(?:>|<|>=|<=|=) *\d+$`) +var floatFilterPattern = regexp.MustCompile(`^(?:>|<|>=|<=|=) *\d+(?:\.\d+)?$`) + +func (c *client) doSearch( + words string, + filter [][]string, + sort string, + limit, offset int, +) (*meiliSearchResponse, error) { + if limit == 0 { + limit = 10 + } else if limit > 50 { + limit = 50 + } + + var sortOpt []string + switch sort { + case "", "match": + case "score": + sortOpt = []string{"score:desc"} + case "heat": + sortOpt = []string{"heat:desc"} + case "rank": + sortOpt = []string{"rank:asc"} + default: + return nil, res.BadRequest("sort not supported") + } + + raw, err := c.index.SearchRaw(words, &meilisearch.SearchRequest{ + Offset: int64(offset), + Limit: int64(limit), + Filter: filter, + Sort: sortOpt, + }) + if err != nil { + return nil, errgo.Wrap(err, "meilisearch search") + } + + var r meiliSearchResponse + if err := json.Unmarshal(*raw, &r); err != nil { + return nil, errgo.Wrap(err, "json.Unmarshal") + } + + return &r, nil +} + +type meiliSearchResponse struct { + Hits json.RawMessage `json:"hits"` + EstimatedTotalHits int64 `json:"estimatedTotalHits"` //nolint:tagliatelle +} + +func filterToMeiliFilter(req ReqFilter) ([][]string, error) { + var filter = make([][]string, 0, 6+len(req.Tag)) + + // OR + + if len(req.AirDate) != 0 { + dateFilters, err := parseDateFilter(req.AirDate) + if err != nil { + return nil, err + } + filter = append(filter, dateFilters...) + } + + if len(req.Type) != 0 { + filter = append(filter, slice.Map(req.Type, func(s model.SubjectType) string { + return fmt.Sprintf("type = %d", s) + })) + } + + if req.NSFW.Set { + if !req.NSFW.Value { + filter = append(filter, []string{fmt.Sprintf("nsfw = %t", req.NSFW.Value)}) + } + } + + for _, t := range req.MetaTags { + filter = append(filter, []string{"meta_tag = " + strconv.Quote(t)}) + } + + // AND + + for _, t := range req.Tag { + filter = append(filter, []string{"tag = " + strconv.Quote(t)}) + } + + for _, s := range req.Rank { + if !intFilterPattern.MatchString(s) { + return nil, res.BadRequest(fmt.Sprintf( + `invalid rank filter: %q, should be in the format of "^(>|<|>=|<=|=) *\d+$"`, s)) + } + filter = append(filter, []string{"rank " + s}) + } + + for _, s := range req.Score { + if !floatFilterPattern.MatchString(s) { + return nil, res.BadRequest(fmt.Sprintf( + `invalid score filter: %q, should be in the format of "^(>|<|>=|<=|=) *\d+(\.\d)?$"`, s)) + } + + filter = append(filter, []string{"score " + s}) + } + + for _, s := range req.RatingCount { + if !intFilterPattern.MatchString(s) { + return nil, res.BadRequest(fmt.Sprintf( + `invalid rating_count filter: %q, should be in the format of "^(>|<|>=|<=|=) *\d+$"`, s)) + } + filter = append(filter, []string{"rating_count " + s}) + } + + return filter, nil +} + +// parse date filter like `<2020-01-20`, `>=2020-01-23`. +func parseDateFilter(filters []string) ([][]string, error) { + var result = make([][]string, 0, len(filters)) + + for _, s := range filters { + switch { + case strings.HasPrefix(s, ">="): + if v, ok := parseDateValOk(s[2:]); ok { + result = append(result, []string{fmt.Sprintf("date >= %d", v)}) + } else { + return nil, res.BadRequest(fmt.Sprintf( + `invalid date filter: %q, date should be in the format of ">= YYYY-MM-DD"`, s)) + } + case strings.HasPrefix(s, ">"): + if v, ok := parseDateValOk(s[1:]); ok { + result = append(result, []string{fmt.Sprintf("date > %d", v)}) + } else { + return nil, res.BadRequest(fmt.Sprintf( + `invalid date filter: %q, date should be in the format of "> YYYY-MM-DD"`, s)) + } + case strings.HasPrefix(s, "<="): + if v, ok := parseDateValOk(s[2:]); ok { + result = append(result, []string{fmt.Sprintf("date <= %d", v)}) + } else { + return nil, res.BadRequest(fmt.Sprintf( + `invalid date filter: %q, date should be in the format of "<= YYYY-MM-DD"`, s)) + } + case strings.HasPrefix(s, "<"): + if v, ok := parseDateValOk(s[1:]); ok { + result = append(result, []string{fmt.Sprintf("date < %d", v)}) + } else { + return nil, res.BadRequest(fmt.Sprintf( + `invalid date filter: %q, date should be in the format of "< YYYY-MM-DD"`, s)) + } + default: + if v, ok := parseDateValOk(s); ok { + result = append(result, []string{fmt.Sprintf("date = %d", v)}) + } else { + return nil, res.BadRequest(fmt.Sprintf( + `invalid date filter: %q, date should be in the format of "YYYY-MM-DD"`, s)) + } + } + } + + return result, nil +} + +func parseDateValOk(date string) (int, bool) { + if len(date) < 10 { + return 0, false + } + + // 2008-10-05 format + if !isDigitsOnly(date[:4]) || + date[4] != '-' || !isDigitsOnly(date[5:7]) || + date[7] != '-' || !isDigitsOnly(date[8:10]) { + return 0, false + } + + v, err := strconv.Atoi(date[:4]) + if err != nil { + return 0, false + } + val := v * 10000 + + v, err = strconv.Atoi(date[5:7]) + if err != nil { + return 0, false + } + val += v * 100 + + v, err = strconv.Atoi(date[8:10]) + if err != nil { + return 0, false + } + val += v + + return val, true +} + +func isDigitsOnly(s string) bool { + for _, c := range s { + if c < '0' || c > '9' { + return false + } + } + return true +} + +func toResponseSubject(s model.Subject, metaTags []tag.Tag) ResponseSubject { + images := res.SubjectImage(s.Image) + return ResponseSubject{ + ID: s.ID, + Image: images.Large, + Images: images, + Summary: s.Summary, + Name: s.Name, + Platform: res.PlatformString(s), + NameCN: s.NameCN, + Date: null.NilString(s.Date), + Infobox: compat.V0Wiki(wiki.ParseOmitError(s.Infobox).NonZero()), + Volumes: s.Volumes, + TotalEpisodes: int64(s.Eps), + Redirect: s.Redirect, + Eps: s.Eps, + MetaTags: lo.Map(metaTags, func(item tag.Tag, index int) string { + return item.Name + }), + Tags: slice.Map(s.Tags, func(tag model.Tag) res.SubjectTag { + return res.SubjectTag{ + Name: tag.Name, + Count: tag.Count, + TotalCont: tag.TotalCount, + } + }), + Collection: res.SubjectCollectionStat{ + OnHold: s.OnHold, + Wish: s.Wish, + Dropped: s.Dropped, + Collect: s.Collect, + Doing: s.Doing, + }, + TypeID: s.TypeID, + Series: s.Series, + Locked: s.Locked(), + NSFW: s.NSFW, + Rating: res.Rating{ + Rank: s.Rating.Rank, + Total: s.Rating.Total, + Count: res.Count{ + Field1: s.Rating.Count.Field1, + Field2: s.Rating.Count.Field2, + Field3: s.Rating.Count.Field3, + Field4: s.Rating.Count.Field4, + Field5: s.Rating.Count.Field5, + Field6: s.Rating.Count.Field6, + Field7: s.Rating.Count.Field7, + Field8: s.Rating.Count.Field8, + Field9: s.Rating.Count.Field9, + Field10: s.Rating.Count.Field10, + }, + Score: s.Rating.Score, + }, + } +} diff --git a/internal/search/handle_internal_test.go b/internal/search/subject/handle_internal_test.go similarity index 78% rename from internal/search/handle_internal_test.go rename to internal/search/subject/handle_internal_test.go index 17f2bb668..739a114c1 100644 --- a/internal/search/handle_internal_test.go +++ b/internal/search/subject/handle_internal_test.go @@ -12,7 +12,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see -package search +package subject import ( "testing" @@ -25,13 +25,18 @@ import ( func Test_ReqFilterToMeiliFilter(t *testing.T) { t.Parallel() - actual := filterToMeiliFilter(ReqFilter{ - Tag: []string{"a", "b"}, - NSFW: null.Bool{Set: false}, + actual, err := filterToMeiliFilter(ReqFilter{ + Tag: []string{"a", "b"}, + RatingCount: []string{">=100"}, + NSFW: null.Bool{Set: true, Value: false}, }) + require.NoError(t, err) + require.Equal(t, [][]string{ + {`nsfw = false`}, {`tag = "a"`}, {`tag = "b"`}, + {`rating_count >=100`}, }, actual) } diff --git a/internal/search/index_internal_test.go b/internal/search/subject/index_internal_test.go similarity index 76% rename from internal/search/index_internal_test.go rename to internal/search/subject/index_internal_test.go index a9d218441..ab5b5ba95 100644 --- a/internal/search/index_internal_test.go +++ b/internal/search/subject/index_internal_test.go @@ -12,20 +12,24 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see -package search +package subject import ( + "reflect" "sort" "testing" "github.com/stretchr/testify/require" + + "github.com/bangumi/server/internal/search/searcher" ) func TestIndexFilter(t *testing.T) { t.Parallel() - actual := *(getAttributes("filterable")) - expected := []string{"date", "score", "rank", "type", "nsfw", "tag"} + rt := reflect.TypeOf(document{}) + actual := *(searcher.GetAttributes(rt, "filterable")) + expected := []string{"date", "meta_tag", "rating_count", "score", "rank", "type", "nsfw", "tag"} sort.Strings(expected) sort.Strings(actual) diff --git a/internal/subject/cache_repo.go b/internal/subject/cache_repo.go index 72ff9da52..c172a87b3 100644 --- a/internal/subject/cache_repo.go +++ b/internal/subject/cache_repo.go @@ -39,6 +39,11 @@ type cacheRepo struct { log *zap.Logger } +const ( + browseCacheTTLFirst = 24 * time.Hour + browseCacheTTLOther = time.Hour +) + func (r cacheRepo) Get(ctx context.Context, id model.SubjectID, filter Filter) (model.Subject, error) { var key = cachekey.Subject(id) @@ -71,6 +76,66 @@ func (r cacheRepo) GetByIDs( return r.repo.GetByIDs(ctx, ids, filter) } +func (r cacheRepo) Count(ctx context.Context, filter BrowseFilter) (int64, error) { + hash, err := filter.Hash() + if err != nil { + return 0, err + } + key := cachekey.SubjectBrowseCount(hash) + + var s int64 + ok, err := r.cache.Get(ctx, key, &s) + if err != nil { + return s, errgo.Wrap(err, "cache.Get") + } + if ok { + return s, nil + } + + s, err = r.repo.Count(ctx, filter) + if err != nil { + return s, err + } + if e := r.cache.Set(ctx, key, s, 10*time.Minute); e != nil { + r.log.Error("can't set response to cache", zap.Error(e)) + } + + return s, nil +} + +func (r cacheRepo) Browse( + ctx context.Context, filter BrowseFilter, limit, offset int, +) ([]model.Subject, error) { + hash, err := filter.Hash() + if err != nil { + return nil, err + } + key := cachekey.SubjectBrowse(hash, limit, offset) + + var subjects []model.Subject + ok, err := r.cache.Get(ctx, key, &subjects) + if err != nil { + return nil, errgo.Wrap(err, "cache.Get") + } + if ok { + return subjects, nil + } + + subjects, err = r.repo.Browse(ctx, filter, limit, offset) + if err != nil { + return nil, err + } + ttl := browseCacheTTLFirst + if offset > 0 { + ttl = browseCacheTTLOther + } + if e := r.cache.Set(ctx, key, subjects, ttl); e != nil { + r.log.Error("can't set response to cache", zap.Error(e)) + } + + return subjects, nil +} + func (r cacheRepo) GetPersonRelated( ctx context.Context, personID model.PersonID, ) ([]domain.SubjectPersonRelation, error) { diff --git a/internal/subject/domain.go b/internal/subject/domain.go index 2fe2c3e8e..cc47e95eb 100644 --- a/internal/subject/domain.go +++ b/internal/subject/domain.go @@ -16,6 +16,8 @@ package subject import ( "context" + "fmt" + "hash/fnv" "github.com/bangumi/server/domain" "github.com/bangumi/server/internal/model" @@ -23,10 +25,49 @@ import ( ) type Filter struct { - // if nsfw subject are allowed NSFW null.Bool } +type BrowseFilter struct { + NSFW null.Bool + Type uint8 + Category null.Uint16 + Series null.Bool + Platform null.String + Sort null.String + Year null.Int32 + Month null.Int8 +} + +func (f BrowseFilter) Hash() (string, error) { + h := fnv.New64a() + + fmt.Fprintf(h, "type:%v", f.Type) + if f.NSFW.Set { + fmt.Fprintf(h, "nsfw:%v", f.NSFW) + } + if f.Category.Set { + fmt.Fprintf(h, "category:%v", f.Category) + } + if f.Series.Set { + fmt.Fprintf(h, "series:%v", f.Series) + } + if f.Platform.Set { + fmt.Fprintf(h, "platform:%v", f.Platform) + } + if f.Sort.Set { + fmt.Fprintf(h, "sort:%v", f.Sort) + } + if f.Year.Set { + fmt.Fprintf(h, "year:%v", f.Year) + } + if f.Month.Set { + fmt.Fprintf(h, "month:%v", f.Month) + } + + return fmt.Sprintf("%x", h.Sum64()), nil +} + type Repo interface { read } @@ -40,6 +81,9 @@ type read interface { Get(ctx context.Context, id model.SubjectID, filter Filter) (model.Subject, error) GetByIDs(ctx context.Context, ids []model.SubjectID, filter Filter) (map[model.SubjectID]model.Subject, error) + Count(ctx context.Context, filter BrowseFilter) (int64, error) + Browse(ctx context.Context, filter BrowseFilter, limit, offset int) ([]model.Subject, error) + GetPersonRelated(ctx context.Context, personID model.PersonID) ([]domain.SubjectPersonRelation, error) GetCharacterRelated(ctx context.Context, characterID model.CharacterID) ([]domain.SubjectCharacterRelation, error) GetSubjectRelated(ctx context.Context, subjectID model.SubjectID) ([]domain.SubjectInternalRelation, error) diff --git a/internal/subject/mysq_repository_compat.go b/internal/subject/mysq_repository_compat.go index 18954280e..afc8d7017 100644 --- a/internal/subject/mysq_repository_compat.go +++ b/internal/subject/mysq_repository_compat.go @@ -16,28 +16,31 @@ package subject import ( "github.com/trim21/errgo" - "github.com/trim21/go-phpserialize" "github.com/bangumi/server/internal/model" "github.com/bangumi/server/internal/pkg/generic/slice" + "github.com/bangumi/server/internal/pkg/serialize" ) type Tag struct { - Name *string `php:"tag_name"` - Count int `php:"result,string"` + Name *string `php:"tag_name" json:"tag_name"` + Count uint `php:"result,string" json:"result,string"` + TotalCount uint `php:"tag_results,string" json:"tag_results,string"` } -func parseTags(b []byte) ([]model.Tag, error) { +func ParseTags(b []byte) ([]model.Tag, error) { var tags []Tag - err := phpserialize.Unmarshal(b, &tags) - if err != nil { - return nil, errgo.Wrap(err, "parseTags: phpserialize.Unmarshal") + if len(b) != 0 { + err := serialize.Decode(b, &tags) + if err != nil { + return nil, errgo.Wrap(err, "ParseTags: serialize.Decode") + } } return slice.MapFilter(tags, func(item Tag) (model.Tag, bool) { if item.Name == nil { return model.Tag{}, false } - return model.Tag{Name: *item.Name, Count: item.Count}, true + return model.Tag{Name: *item.Name, Count: item.Count, TotalCount: item.TotalCount}, true }), nil } diff --git a/internal/subject/mysql_repository.go b/internal/subject/mysql_repository.go index 15f88708b..a6858deb0 100644 --- a/internal/subject/mysql_repository.go +++ b/internal/subject/mysql_repository.go @@ -26,6 +26,7 @@ import ( "github.com/bangumi/server/dal/dao" "github.com/bangumi/server/dal/query" + "github.com/bangumi/server/dal/utiltype" "github.com/bangumi/server/domain" "github.com/bangumi/server/domain/gerr" "github.com/bangumi/server/internal/model" @@ -53,7 +54,6 @@ func (r mysqlRepo) Get(ctx context.Context, id model.SubjectID, filter Filter) ( return model.Subject{}, fmt.Errorf("%w: %d", gerr.ErrNotFound, id) } - r.log.Error("unexpected error happened", zap.Error(err)) return model.Subject{}, errgo.Wrap(err, "dal") } @@ -66,7 +66,7 @@ func ConvertDao(s *dao.Subject) (model.Subject, error) { date = s.Fields.Date.Format("2006-01-02") } - tags, err := parseTags(s.Fields.Tags) + tags, err := ParseTags(s.Fields.Tags) if err != nil { return model.Subject{}, err } @@ -75,12 +75,13 @@ func ConvertDao(s *dao.Subject) (model.Subject, error) { Redirect: s.Fields.Redirect, Date: date, ID: s.ID, - Name: s.Name, - NameCN: s.NameCN, + Name: string(s.Name), + NameCN: string(s.NameCN), + MetaTags: s.FieldMetaTags, TypeID: s.TypeID, Image: s.Image, PlatformID: s.Platform, - Infobox: s.Infobox, + Infobox: string(s.Infobox), Summary: s.Summary, Volumes: s.Volumes, Eps: s.Eps, @@ -136,8 +137,6 @@ func (r mysqlRepo) GetPersonRelated( Joins(r.q.PersonSubjects.Person). Where(r.q.PersonSubjects.PersonID.Eq(personID)).Find() if err != nil { - r.log.Error("unexpected error happened", zap.Error(err)) - return nil, errgo.Wrap(err, "dal") } @@ -147,6 +146,7 @@ func (r mysqlRepo) GetPersonRelated( SubjectID: relation.SubjectID, PersonID: relation.PersonID, TypeID: relation.PrsnPosition, + Eps: relation.PrsnAppearEps, }) } @@ -161,7 +161,6 @@ func (r mysqlRepo) GetCharacterRelated( Joins(r.q.CharacterSubjects.Subject). Where(r.q.CharacterSubjects.CharacterID.Eq(characterID)).Find() if err != nil { - r.log.Error("unexpected error happened", zap.Error(err)) return nil, errgo.Wrap(err, "dal") } @@ -185,7 +184,6 @@ func (r mysqlRepo) GetSubjectRelated( Joins(r.q.SubjectRelation.Subject).Where(r.q.SubjectRelation.SubjectID.Eq(subjectID)). Order(r.q.SubjectRelation.Order).Find() if err != nil { - r.log.Error("unexpected error happened", zap.Error(err)) return nil, errgo.Wrap(err, "dal") } @@ -215,7 +213,6 @@ func (r mysqlRepo) GetByIDs( records, err := q.Find() if err != nil { - r.log.Error("unexpected error happened", zap.Error(err)) return nil, errgo.Wrap(err, "dal") } @@ -231,6 +228,93 @@ func (r mysqlRepo) GetByIDs( return result, nil } +func (r mysqlRepo) Count( + ctx context.Context, + filter BrowseFilter) (int64, error) { + q := r.q.Subject.WithContext(ctx).Joins(r.q.Subject.Fields).Join( + r.q.SubjectField, r.q.Subject.ID.EqCol(r.q.SubjectField.Sid), + ).Where(r.q.Subject.TypeID.Eq(filter.Type), r.q.SubjectField.Redirect.Eq(0)) + if filter.NSFW.Set { + q = q.Where(r.q.Subject.Nsfw.Is(filter.NSFW.Value)) + } + if filter.Category.Set { + q = q.Where(r.q.Subject.Platform.Eq(filter.Category.Value)) + } + if filter.Series.Set { + q = q.Where(r.q.Subject.Series.Is(filter.Series.Value)) + } + if filter.Platform.Set { + q = q.Where(r.q.Subject.Infobox.Like(utiltype.HTMLEscapedString(fmt.Sprintf("%%[%s]%%", filter.Platform.Value)))) + } + if filter.Year.Set { + q = q.Where(r.q.SubjectField.Year.Eq(filter.Year.Value)) + } + if filter.Month.Set { + q = q.Where(r.q.SubjectField.Mon.Eq(filter.Month.Value)) + } + + if filter.Sort.Set { + switch filter.Sort.Value { + case "date": + q = q.Order(r.q.SubjectField.Date.Desc()) + case "rank": + q = q.Where(r.q.SubjectField.Rank.Gt(0)).Order(r.q.SubjectField.Rank) + } + } + + return q.Count() +} + +func (r mysqlRepo) Browse( + ctx context.Context, filter BrowseFilter, limit, offset int, +) ([]model.Subject, error) { + q := r.q.Subject.WithContext(ctx).Joins(r.q.Subject.Fields).Join( + r.q.SubjectField, r.q.Subject.ID.EqCol(r.q.SubjectField.Sid), + ).Where(r.q.Subject.TypeID.Eq(filter.Type), r.q.SubjectField.Redirect.Eq(0)) + if filter.NSFW.Set { + q = q.Where(r.q.Subject.Nsfw.Is(filter.NSFW.Value)) + } + if filter.Category.Set { + q = q.Where(r.q.Subject.Platform.Eq(filter.Category.Value)) + } + if filter.Series.Set { + q = q.Where(r.q.Subject.Series.Is(filter.Series.Value)) + } + if filter.Platform.Set { + q = q.Where(r.q.Subject.Infobox.Like(utiltype.HTMLEscapedString(fmt.Sprintf("%%[%s]%%", filter.Platform.Value)))) + } + if filter.Year.Set { + q = q.Where(r.q.SubjectField.Year.Eq(filter.Year.Value)) + } + if filter.Month.Set { + q = q.Where(r.q.SubjectField.Mon.Eq(filter.Month.Value)) + } + + if filter.Sort.Set { + switch filter.Sort.Value { + case "date": + q = q.Order(r.q.SubjectField.Date.Desc()) + case "rank": + q = q.Where(r.q.SubjectField.Rank.Gt(0)).Order(r.q.SubjectField.Rank) + } + } + + subjects, err := q.Limit(limit).Offset(offset).Find() + if err != nil { + return nil, errgo.Wrap(err, "dal") + } + + var result = make([]model.Subject, len(subjects)) + for i, subject := range subjects { + result[i], err = ConvertDao(subject) + if err != nil { + return nil, err + } + } + + return result, nil +} + func (r mysqlRepo) GetActors( ctx context.Context, subjectID model.SubjectID, @@ -241,7 +325,6 @@ func (r mysqlRepo) GetActors( Order(r.q.Cast.PersonID). Find() if err != nil { - r.log.Error("unexpected error happened", zap.Error(err)) return nil, errgo.Wrap(err, "dal") } diff --git a/internal/subject/mysql_repository_internal_test.go b/internal/subject/mysql_repository_internal_test.go index e0f149b6e..dab4be05f 100644 --- a/internal/subject/mysql_repository_internal_test.go +++ b/internal/subject/mysql_repository_internal_test.go @@ -37,7 +37,7 @@ func TestParseTags(t *testing.T) { require.NoError(t, json.Unmarshal(raw, &testCases)) for i, tc := range testCases { - tags, err := parseTags([]byte(tc.FieldTags)) + tags, err := ParseTags([]byte(tc.FieldTags)) require.NoError(t, err) require.Truef(t, len(tags) > 0, "should parse tags") diff --git a/internal/subject/mysql_repository_test.go b/internal/subject/mysql_repository_test.go index 3dfc5b4fd..8233fd48d 100644 --- a/internal/subject/mysql_repository_test.go +++ b/internal/subject/mysql_repository_test.go @@ -62,6 +62,79 @@ func TestMysqlRepo_Get_filter(t *testing.T) { require.ErrorIs(t, err, gerr.ErrNotFound) } +func TestBrowse(t *testing.T) { + test.RequireEnv(t, test.EnvMysql) + t.Parallel() + + repo := getRepo(t) + + filter := subject.BrowseFilter{ + Type: 2, + } + s, err := repo.Browse(context.Background(), filter, 30, 0) + require.NoError(t, err) + require.Equal(t, 30, len(s)) + for _, item := range s { + require.Zero(t, item.Redirect) + } + + filter = subject.BrowseFilter{ + Type: 1, + Category: null.New(uint16(1003)), + } + s, err = repo.Browse(context.Background(), filter, 30, 0) + require.NoError(t, err) + require.Equal(t, 2, len(s)) + for _, item := range s { + require.Zero(t, item.Redirect) + } + + filter = subject.BrowseFilter{ + Type: 2, + Year: null.New(int32(2008)), + } + s, err = repo.Browse(context.Background(), filter, 30, 0) + require.NoError(t, err) + require.Equal(t, 4, len(s)) + for _, item := range s { + require.Zero(t, item.Redirect) + } + + filter = subject.BrowseFilter{ + Type: 3, + Sort: null.New("rank"), + } + s, err = repo.Browse(context.Background(), filter, 30, 0) + require.NoError(t, err) + require.Equal(t, 7, len(s)) + lastRank := uint32(0) + for i, item := range s { + require.NotZero(t, item.Rating.Rank) + if i > 0 { + require.GreaterOrEqual(t, item.Rating.Rank, lastRank) + } + lastRank = item.Rating.Rank + } + for _, item := range s { + require.Zero(t, item.Redirect) + } + + filter = subject.BrowseFilter{ + Type: 4, + Platform: null.New("PS3"), + Sort: null.New("date"), + } + s, err = repo.Browse(context.Background(), filter, 30, 0) + require.NoError(t, err) + require.Equal(t, 3, len(s)) + for _, item := range s { + require.Zero(t, item.Redirect) + } + require.Equal(t, model.SubjectID(7), s[0].ID) + require.Equal(t, model.SubjectID(6), s[1].ID) + require.Equal(t, model.SubjectID(13), s[2].ID) +} + func TestMysqlRepo_GetByIDs(t *testing.T) { test.RequireEnv(t, test.EnvMysql) t.Parallel() diff --git a/internal/tag/cache_repo.go b/internal/tag/cache_repo.go new file mode 100644 index 000000000..47472e588 --- /dev/null +++ b/internal/tag/cache_repo.go @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +package tag + +import ( + "context" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/samber/lo" + "github.com/trim21/errgo" + "go.uber.org/zap" + + "github.com/bangumi/server/internal/cachekey" + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/pkg/cache" +) + +const cacheTTL = time.Hour * 24 + +func NewCachedRepo(c cache.RedisCache, r Repo, log *zap.Logger) CachedRepo { + return cacheRepo{cache: c, repo: r, log: log.Named("subject.CachedRepo")} +} + +var _ CachedRepo = cacheRepo{} + +type cacheRepo struct { + cache cache.RedisCache + repo Repo + log *zap.Logger +} + +type cachedTags struct { + ID model.SubjectID + Tags []Tag +} + +//nolint:gochecknoglobals +var CachedCount = prometheus.NewCounter(prometheus.CounterOpts{ + Subsystem: "chii", + Name: "query_cached_count_total", + Help: "cached sql query count total", + ConstLabels: map[string]string{"repo": "meta_tags"}, +}) + +//nolint:gochecknoglobals +var TotalCount = prometheus.NewCounter(prometheus.CounterOpts{ + Subsystem: "chii", + Name: "query_count_total", + Help: "sql query count total", + ConstLabels: map[string]string{"repo": "meta_tags"}, +}) + +//nolint:gochecknoinits +func init() { + prometheus.MustRegister(CachedCount) + prometheus.MustRegister(TotalCount) +} + +// also need to change version in [cachekey.SubjectMetaTag] if schema is changed. + +func (r cacheRepo) Get(ctx context.Context, id model.SubjectID, typeID model.SubjectType) ([]Tag, error) { + TotalCount.Add(1) + var key = cachekey.SubjectMetaTag(id) + + var s cachedTags + ok, err := r.cache.Get(ctx, key, &s) + if err != nil { + return s.Tags, errgo.Wrap(err, "cache.Get") + } + + if ok { + CachedCount.Add(1) + return s.Tags, nil + } + + tags, err := r.repo.Get(ctx, id, typeID) + if err != nil { + return tags, err + } + + if e := r.cache.Set(ctx, key, cachedTags{ID: id, Tags: tags}, cacheTTL); e != nil { + r.log.Error("can't set response to cache", zap.Error(e)) + } + + return tags, nil +} + +func (r cacheRepo) GetByIDs(ctx context.Context, ids []model.SubjectID) (map[model.SubjectID][]Tag, error) { + result := make(map[model.SubjectID][]Tag, len(ids)) + if len(ids) == 0 { + return result, nil + } + + TotalCount.Add(float64(len(ids))) + + var tags []cachedTags + + err := r.cache.MGet(ctx, lo.Map(ids, func(item model.SubjectID, index int) string { + return cachekey.SubjectMetaTag(item) + }), &tags) + if err != nil { + return nil, errgo.Wrap(err, "cache.MGet") + } + + CachedCount.Add(float64(len(tags))) + for _, tag := range tags { + result[tag.ID] = tag.Tags + } + + var missing = make([]model.SubjectID, 0, len(ids)) + for _, id := range ids { + if _, ok := result[id]; !ok { + missing = append(missing, id) + } + } + + if len(missing) == 0 { + return result, nil + } + + missingFromCache, err := r.repo.GetByIDs(ctx, missing) + if err != nil { + return nil, err + } + for id, tag := range missingFromCache { + result[id] = tag + err = r.cache.Set(ctx, cachekey.SubjectMetaTag(id), cachedTags{ + ID: id, + Tags: tag, + }, cacheTTL) + if err != nil { + return nil, errgo.Wrap(err, "cache.Set") + } + } + + return result, nil +} diff --git a/pkg/wiki/parser_internal_test.go b/internal/tag/cache_repo_test.go similarity index 52% rename from pkg/wiki/parser_internal_test.go rename to internal/tag/cache_repo_test.go index a2b06aeb8..25caea42f 100644 --- a/pkg/wiki/parser_internal_test.go +++ b/internal/tag/cache_repo_test.go @@ -12,40 +12,46 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see -package wiki +package tag_test import ( + "context" "testing" "github.com/stretchr/testify/require" + "go.uber.org/fx" + + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/pkg/test" + "github.com/bangumi/server/internal/tag" ) -func Test_readArrayItem(t *testing.T) { - t.Parallel() +func getCacheRepo(t *testing.T) tag.CachedRepo { + t.Helper() - key, value, err := readArrayItem("[k|v]") + var r tag.CachedRepo - require.NoError(t, err) - require.Equal(t, "k", key) - require.Equal(t, "v", value) + test.Fx(t, fx.Provide(tag.NewCachedRepo, tag.NewMysqlRepo), fx.Populate(&r)) + + return r } -func Test_readArrayItem2(t *testing.T) { +func TestCacheGet(t *testing.T) { + test.RequireEnv(t, test.EnvMysql, test.EnvRedis) t.Parallel() - key, value, err := readArrayItem("[v]") + repo := getCacheRepo(t) + _, err := repo.Get(context.Background(), 8, model.SubjectTypeAnime) require.NoError(t, err) - require.Equal(t, "", key) - require.Equal(t, "v", value) } -func Test_readArrayItem3(t *testing.T) { +func TestCacheGetTags(t *testing.T) { + test.RequireEnv(t, test.EnvMysql, test.EnvRedis) t.Parallel() - key, value, err := readArrayItem("[k|]") + repo := getCacheRepo(t) + _, err := repo.GetByIDs(context.Background(), []model.SubjectID{1, 2, 8}) require.NoError(t, err) - require.Equal(t, "k", key) - require.Equal(t, "", value) } diff --git a/internal/notification/domain.go b/internal/tag/domain.go similarity index 61% rename from internal/notification/domain.go rename to internal/tag/domain.go index 86d895e83..c8ba4218f 100644 --- a/internal/notification/domain.go +++ b/internal/tag/domain.go @@ -12,7 +12,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see -package notification +package tag import ( "context" @@ -20,6 +20,28 @@ import ( "github.com/bangumi/server/internal/model" ) +// CatSubject 条目tag. +const CatSubject = 0 + +// CatMeta 官方tag. +const CatMeta = 3 + +type Tag struct { + Name string + Count uint + // TotalCount count for all tags including all subject + TotalCount uint +} + +type CachedRepo interface { + read +} + type Repo interface { - Count(ctx context.Context, userID model.UserID) (int64, error) + read +} + +type read interface { + Get(ctx context.Context, id model.SubjectID, typeID model.SubjectType) ([]Tag, error) + GetByIDs(ctx context.Context, ids []model.SubjectID) (map[model.SubjectID][]Tag, error) } diff --git a/internal/tag/mysql_repo.go b/internal/tag/mysql_repo.go new file mode 100644 index 000000000..c0ec7d745 --- /dev/null +++ b/internal/tag/mysql_repo.go @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +package tag + +import ( + "context" + + "github.com/jmoiron/sqlx" + "go.uber.org/zap" + + "github.com/bangumi/server/dal/query" + "github.com/bangumi/server/internal/model" +) + +func NewMysqlRepo(q *query.Query, log *zap.Logger, db *sqlx.DB) (Repo, error) { + return mysqlRepo{q: q, log: log.Named("tag.mysqlRepo"), db: db}, nil +} + +type mysqlRepo struct { + q *query.Query + log *zap.Logger + db *sqlx.DB +} + +func (r mysqlRepo) Get(ctx context.Context, id model.SubjectID, typeID model.SubjectType) ([]Tag, error) { + var s []struct { + Tid uint `db:"tlt_tid"` + Name string `db:"tag_name"` + TotalCount uint `db:"tag_results"` + } + + err := r.db.SelectContext(ctx, &s, ` + select tlt_tid, tag_name, tag_results + from chii_tag_neue_list + inner join chii_tag_neue_index on tlt_tid = tag_id and tag_cat = tlt_cat and tag_type = tlt_type + where tlt_uid = 0 and tlt_cat = ? and tlt_mid = ? and tlt_type = ? + `, CatSubject, id, typeID) + if err != nil { + return nil, err + } + + tags := make([]Tag, len(s)) + for i, t := range s { + tags[i] = Tag{ + Name: t.Name, + TotalCount: t.TotalCount, + } + } + + return tags, nil +} + +func (r mysqlRepo) GetByIDs(ctx context.Context, ids []model.SubjectID) (map[model.SubjectID][]Tag, error) { + var s []struct { + Tid uint `db:"tlt_tid"` + Name string `db:"tag_name"` + TotalCount uint `db:"tag_results"` + Mid model.SubjectID `db:"tlt_mid"` + } + + q, v, err := sqlx.In(` + select tlt_tid, tag_name, tag_results, tlt_mid + from chii_tag_neue_list + inner join chii_tag_neue_index on tlt_tid = tag_id + where tlt_uid = 0 and tag_cat = ? and tlt_mid IN (?) + `, CatSubject, ids) + if err != nil { + return nil, err + } + + err = r.db.SelectContext(ctx, &s, q, v...) + if err != nil { + return nil, err + } + + tags := make(map[model.SubjectID][]Tag, len(s)) + for _, t := range s { + tags[t.Mid] = append(tags[t.Mid], Tag{ + Name: t.Name, + TotalCount: t.TotalCount, + }) + } + + // set empty slice for subjects without tags + // this help we cache them. + for _, id := range ids { + if _, ok := tags[id]; !ok { + tags[id] = []Tag{} + } + } + + return tags, nil +} diff --git a/internal/notification/mysql_repository_test.go b/internal/tag/mysql_repo_test.go similarity index 61% rename from internal/notification/mysql_repository_test.go rename to internal/tag/mysql_repo_test.go index 9295b846b..f71513551 100644 --- a/internal/notification/mysql_repository_test.go +++ b/internal/tag/mysql_repo_test.go @@ -12,34 +12,48 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see -package notification_test +package tag_test import ( "context" "testing" + "github.com/jmoiron/sqlx" + "github.com/samber/lo" "github.com/stretchr/testify/require" "go.uber.org/zap" "github.com/bangumi/server/dal/query" - "github.com/bangumi/server/internal/notification" + "github.com/bangumi/server/internal/model" "github.com/bangumi/server/internal/pkg/test" + "github.com/bangumi/server/internal/tag" ) -func getRepo(t *testing.T) notification.Repo { +func getRepo(t *testing.T) tag.Repo { t.Helper() - repo, err := notification.NewMysqlRepo(query.Use(test.GetGorm(t)), zap.NewNop()) + q := query.Use(test.GetGorm(t)) + repo, err := tag.NewMysqlRepo(q, zap.NewNop(), sqlx.NewDb(lo.Must(q.DB().DB()), "mysql")) require.NoError(t, err) return repo } -func TestCount(t *testing.T) { +func TestGet(t *testing.T) { test.RequireEnv(t, test.EnvMysql) t.Parallel() repo := getRepo(t) - ctx := context.Background() - _, err := repo.Count(ctx, 1) + + _, err := repo.Get(context.Background(), 8, model.SubjectTypeAnime) + require.NoError(t, err) +} + +func TestGetTags(t *testing.T) { + test.RequireEnv(t, test.EnvMysql) + t.Parallel() + + repo := getRepo(t) + + _, err := repo.GetByIDs(context.Background(), []model.SubjectID{1, 2, 8}) require.NoError(t, err) } diff --git a/internal/timeline/domain.go b/internal/timeline/domain.go index c480b38e5..cf46a1af7 100644 --- a/internal/timeline/domain.go +++ b/internal/timeline/domain.go @@ -29,6 +29,7 @@ type Service interface { u model.UserID, sbj model.Subject, collect collection.SubjectCollection, + collectID uint64, comment string, rate uint8, ) error @@ -38,6 +39,7 @@ type Service interface { u auth.Auth, sbj model.Subject, episode episode.Episode, + t collection.EpisodeCollection, ) error ChangeSubjectProgress( diff --git a/internal/timeline/grpc.go b/internal/timeline/grpc.go deleted file mode 100644 index 1f41aefb2..000000000 --- a/internal/timeline/grpc.go +++ /dev/null @@ -1,178 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package timeline - -import ( - "context" - "fmt" - "time" - - "github.com/trim21/errgo" - clientv3 "go.etcd.io/etcd/client/v3" - "go.etcd.io/etcd/client/v3/naming/resolver" - "go.uber.org/zap" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - - "github.com/bangumi/server/config" - "github.com/bangumi/server/dal/query" - pb "github.com/bangumi/server/generated/proto/go/api/v1" - "github.com/bangumi/server/internal/auth" - "github.com/bangumi/server/internal/collections/domain/collection" - "github.com/bangumi/server/internal/episode" - "github.com/bangumi/server/internal/model" - "github.com/bangumi/server/internal/pkg/logger" -) - -const defaultTimeout = time.Second * 5 - -func NewMysqlRepo(q *query.Query, log *zap.Logger, cfg config.AppConfig) (Service, error) { - rpc, err := newGrpcClient(cfg) - if err != nil { - return nil, err - } - - return mysqlRepo{q: q, log: log.Named("timeline.mysqlRepo"), rpc: rpc}, nil -} - -type mysqlRepo struct { - q *query.Query - log *zap.Logger - rpc pb.TimeLineServiceClient -} - -func (m mysqlRepo) ChangeSubjectProgress(ctx context.Context, u model.UserID, sbj model.Subject, - epsUpdate uint32, volsUpdate uint32) error { - ctx, canal := context.WithTimeout(ctx, defaultTimeout) - defer canal() - - _, err := m.rpc.SubjectProgress(ctx, &pb.SubjectProgressRequest{ - UserId: uint64(u), - Subject: &pb.Subject{ - Id: sbj.ID, - Type: uint32(sbj.TypeID), - Name: sbj.Name, - NameCn: sbj.NameCN, - Image: sbj.Image, - Series: sbj.Series, - VolsTotal: sbj.Volumes, - EpsTotal: sbj.Eps, - }, - EpsUpdate: epsUpdate, - VolsUpdate: volsUpdate, - }) - - return errgo.Wrap(err, "grpc") -} - -func (m mysqlRepo) ChangeSubjectCollection( - ctx context.Context, - u model.UserID, - sbj model.Subject, - collect collection.SubjectCollection, - comment string, - rate uint8, -) error { - ctx, canal := context.WithTimeout(ctx, defaultTimeout) - defer canal() - - _, err := m.rpc.SubjectCollect(ctx, &pb.SubjectCollectRequest{ - UserId: uint64(u), - Subject: &pb.Subject{ - Id: sbj.ID, - Type: uint32(sbj.TypeID), - Name: sbj.Name, - NameCn: sbj.NameCN, - Image: sbj.Image, - Series: false, - VolsTotal: sbj.Volumes, - EpsTotal: sbj.Eps, - }, - Collection: uint32(collect), - Comment: comment, - Rate: uint32(rate), - }) - - if err != nil { - return errgo.Wrap(err, "grpc: timeline.SubjectCollect") - } - - return nil -} - -func (m mysqlRepo) ChangeEpisodeStatus( - ctx context.Context, - u auth.Auth, - sbj model.Subject, - episode episode.Episode, -) error { - ctx, canal := context.WithTimeout(ctx, defaultTimeout) - defer canal() - - _, err := m.rpc.EpisodeCollect(ctx, &pb.EpisodeCollectRequest{ - UserId: uint64(u.ID), - Last: &pb.Episode{ - Id: episode.ID, - Type: uint32(episode.Type), - Name: episode.Name, - NameCn: episode.NameCN, - Sort: float64(episode.Sort), - }, - Subject: &pb.Subject{ - Id: sbj.ID, - Type: uint32(sbj.TypeID), - Name: sbj.Name, - NameCn: sbj.Name, - Image: sbj.Image, - Series: sbj.Series, - VolsTotal: sbj.Volumes, - EpsTotal: sbj.Eps, - }, - }) - - return errgo.Wrap(err, "grpc") -} - -func newGrpcClient(cfg config.AppConfig) (pb.TimeLineServiceClient, error) { - if cfg.EtcdAddr == "" { - logger.Info("no etcd, using nope timeline service") - return noopClient{}, nil - } - - logger.Info("using etcd to discovery timeline services " + cfg.EtcdAddr) - - cli, err := clientv3.NewFromURL(cfg.EtcdAddr) - if err != nil { - return nil, errgo.Wrap(err, "etcd new client") - } - - etcdResolver, err := resolver.NewBuilder(cli) - if err != nil { - return nil, errgo.Wrap(err, "etcd grpc resolver") - } - - conn, err := grpc.Dial( - fmt.Sprintf("etcd:///%s/timeline", cfg.EtcdNamespace), - grpc.WithResolvers(etcdResolver), - grpc.WithTransportCredentials(insecure.NewCredentials()), - ) - if err != nil { - return nil, errgo.Wrap(err, "grpc dail") - } - - c := pb.NewTimeLineServiceClient(conn) - - return c, nil -} diff --git a/internal/timeline/kafka.go b/internal/timeline/kafka.go new file mode 100644 index 000000000..58b545635 --- /dev/null +++ b/internal/timeline/kafka.go @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +package timeline + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/samber/lo" + "github.com/segmentio/kafka-go" + "github.com/trim21/errgo" + + "github.com/bangumi/server/internal/auth" + "github.com/bangumi/server/internal/collections/domain/collection" + "github.com/bangumi/server/internal/episode" + "github.com/bangumi/server/internal/model" +) + +const timelineSourceAPI = 5 +const timelineTopic = "timeline" +const defaultTimeout = time.Second * 5 + +func NewSrv(kafka *kafka.Writer) (Service, error) { + return kafkaClient{kafka: kafka}, nil +} + +type kafkaClient struct { + kafka *kafka.Writer +} + +func (m kafkaClient) ChangeSubjectProgress(ctx context.Context, u model.UserID, sbj model.Subject, + epsUpdate uint32, volsUpdate uint32) error { + ctx, canal := context.WithTimeout(ctx, defaultTimeout) + defer canal() + + return m.writeMessage(ctx, u, timelineValue{ + Op: "progressSubject", + Message: progressSubject{ + UID: u, + CreatedAt: time.Now().Unix(), + Source: timelineSourceAPI, + Subject: tlSubjectCollect{ + ID: int(sbj.ID), + Type: sbj.TypeID, + Eps: int(sbj.Eps), + Volumes: int(sbj.Volumes), + }, + Collect: tlCollect{ + EpsUpdate: &epsUpdate, + VolsUpdate: &volsUpdate, + }, + }, + }) +} + +func (m kafkaClient) ChangeSubjectCollection( + ctx context.Context, + u model.UserID, + sbj model.Subject, + collect collection.SubjectCollection, + collectID uint64, + comment string, + rate uint8, +) error { + ctx, canal := context.WithTimeout(ctx, defaultTimeout) + defer canal() + + return m.writeMessage(ctx, u, timelineValue{ + Op: "subject", + Message: subject{ + UID: u, + CreatedAt: time.Now().Unix(), + Source: timelineSourceAPI, + Subject: tlSubject{ + ID: sbj.ID, + Type: sbj.TypeID, + }, + Collect: tlCollectRating{ + ID: collectID, + Type: collect, + Rate: rate, + Comment: comment, + }, + }, + }) +} + +func (m kafkaClient) ChangeEpisodeStatus( + ctx context.Context, + u auth.Auth, + sbj model.Subject, + episode episode.Episode, + t collection.EpisodeCollection, +) error { + ctx, canal := context.WithTimeout(ctx, defaultTimeout) + defer canal() + + return m.writeMessage( + ctx, + u.ID, + timelineValue{ + Op: "progressEpisode", + Message: progressEpisode{ + UID: u.ID, + Subject: tlSubject{ + ID: sbj.ID, + Type: sbj.TypeID, + }, + Episode: tlEpisode{ + ID: episode.ID, + Status: t, + }, + CreatedAt: time.Now().Unix(), + Source: timelineSourceAPI, + }, + }, + ) +} + +func (m kafkaClient) writeMessage(ctx context.Context, uid model.UserID, value timelineValue) error { + err := m.kafka.WriteMessages(ctx, kafka.Message{ + Topic: timelineTopic, + Key: fmt.Appendf(nil, "%d", uid), + Value: lo.Must(json.Marshal(value)), + }) + + return errgo.Wrap(err, "kafka") +} diff --git a/internal/timeline/noop.go b/internal/timeline/noop.go deleted file mode 100644 index 80810cacc..000000000 --- a/internal/timeline/noop.go +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package timeline - -import ( - "context" - - "google.golang.org/grpc" - - pb "github.com/bangumi/server/generated/proto/go/api/v1" -) - -var _ pb.TimeLineServiceClient = noopClient{} - -type noopClient struct { -} - -func (n noopClient) Hello(_ context.Context, _ *pb.HelloRequest, _ ...grpc.CallOption) (*pb.HelloResponse, error) { - return &pb.HelloResponse{}, nil -} - -func (n noopClient) SubjectCollect(_ context.Context, _ *pb.SubjectCollectRequest, - _ ...grpc.CallOption) (*pb.SubjectCollectResponse, error) { - return &pb.SubjectCollectResponse{Ok: true}, nil -} - -func (n noopClient) SubjectProgress(_ context.Context, _ *pb.SubjectProgressRequest, - _ ...grpc.CallOption) (*pb.SubjectProgressResponse, error) { - return &pb.SubjectProgressResponse{Ok: true}, nil -} - -func (n noopClient) EpisodeCollect(_ context.Context, _ *pb.EpisodeCollectRequest, - _ ...grpc.CallOption) (*pb.EpisodeCollectResponse, error) { - return &pb.EpisodeCollectResponse{Ok: true}, nil -} diff --git a/internal/timeline/type.go b/internal/timeline/type.go new file mode 100644 index 000000000..96cdc4c61 --- /dev/null +++ b/internal/timeline/type.go @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +//nolint:tagliatelle +package timeline + +import ( + "github.com/bangumi/server/internal/collections/domain/collection" + "github.com/bangumi/server/internal/model" +) + +type timelineValue struct { + Op string `json:"op"` + Message any `json:"message"` +} + +type tlEpisode struct { + ID model.EpisodeID `json:"id"` + Status collection.EpisodeCollection `json:"status"` +} + +type tlSubjectCollect struct { + ID int `json:"id"` + Type model.SubjectType `json:"type"` + Eps int `json:"eps"` + Volumes int `json:"volumes"` +} + +type tlCollect struct { + EpsUpdate *uint32 `json:"epsUpdate,omitempty"` + VolsUpdate *uint32 `json:"volsUpdate,omitempty"` +} + +type progressSubject struct { + UID model.UserID `json:"uid"` + Subject tlSubjectCollect `json:"subject"` + Collect tlCollect `json:"collect"` + CreatedAt int64 `json:"createdAt"` + Source uint8 `json:"source"` +} + +type tlCollectRating struct { + ID uint64 `json:"id"` + Type collection.SubjectCollection `json:"type"` + Rate uint8 `json:"rate"` + Comment string `json:"comment"` +} + +type tlSubject struct { + ID model.SubjectID `json:"id"` + Type model.SubjectType `json:"type"` +} + +type subject struct { + UID model.UserID `json:"uid"` + Subject tlSubject `json:"subject"` + Collect tlCollectRating `json:"collect"` + CreatedAt int64 `json:"createdAt"` + Source uint8 `json:"source"` +} + +type progressEpisode struct { + UID model.UserID `json:"uid"` + Subject tlSubject `json:"subject"` + Episode tlEpisode `json:"episode"` + CreatedAt int64 `json:"createdAt"` + Source uint8 `json:"source"` +} diff --git a/internal/user/domain.go b/internal/user/domain.go index d547004c0..c34c2fdd6 100644 --- a/internal/user/domain.go +++ b/internal/user/domain.go @@ -21,6 +21,9 @@ import ( ) type Repo interface { + // GetFullUser find a user by uid. + GetFullUser(ctx context.Context, userID model.UserID) (FullUser, error) + // GetByID find a user by uid. GetByID(ctx context.Context, userID model.UserID) (User, error) // GetByName find a user by username. diff --git a/internal/user/model.go b/internal/user/model.go index fc79bece2..409ea5474 100644 --- a/internal/user/model.go +++ b/internal/user/model.go @@ -17,11 +17,23 @@ package user import ( "time" - "github.com/trim21/go-phpserialize" - "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/pkg/serialize" ) +// FullUser is for current user or admin only. +type FullUser struct { + RegistrationTime time.Time + NickName string + Avatar string + Sign string + UserName string + ID model.UserID + UserGroup GroupID + TimeOffset int8 + Email string +} + type GroupID = uint8 // User is visible for everyone. @@ -42,9 +54,9 @@ func (u User) GetID() model.UserID { type ReceiveFilter uint8 const ( - ReceiveFilterAll ReceiveFilter = iota - ReceiveFilterFriends - ReceiveFilterNone + ReceiveFilterAll ReceiveFilter = 0 + ReceiveFilterFriends ReceiveFilter = 1 + ReceiveFilterNone ReceiveFilter = 2 ) type PrivacySettingsField int @@ -65,9 +77,11 @@ type PrivacySettings struct { func (settings *PrivacySettings) Unmarshal(s []byte) { rawMap := make(map[PrivacySettingsField]ReceiveFilter, 4) - err := phpserialize.Unmarshal(s, &rawMap) - if err != nil { - return + if len(s) != 0 { + err := serialize.Decode(s, &rawMap) + if err != nil { + return + } } settings.ReceivePrivateMessage = rawMap[PrivacyReceivePrivateMessage] diff --git a/internal/user/mysql_repository.go b/internal/user/mysql_repository.go index d288f6504..a85365a79 100644 --- a/internal/user/mysql_repository.go +++ b/internal/user/mysql_repository.go @@ -17,6 +17,7 @@ package user import ( "context" "errors" + "strconv" "time" "github.com/trim21/errgo" @@ -40,14 +41,51 @@ type mysqlRepo struct { log *zap.Logger } +func (m mysqlRepo) GetFullUser(ctx context.Context, userID model.UserID) (FullUser, error) { + u, err := m.q.Member.WithContext(ctx).Where(m.q.Member.ID.Eq(userID)).Take() + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return FullUser{}, gerr.ErrUserNotFound + } + return FullUser{}, errgo.Wrap(err, "dal") + } + + return FullUser{ + UserName: u.Username, + NickName: u.Nickname, + UserGroup: u.Groupid, + Avatar: u.Avatar, + Sign: string(u.Sign), + ID: u.ID, + RegistrationTime: time.Unix(u.Regdate, 0), + TimeOffset: parseTimeOffset(u.Timeoffset), + Email: u.Email, + }, nil +} + +// default time zone GMT+8. +const defaultTimeOffset = 8 + +func parseTimeOffset(s string) int8 { + switch s { + case "", "9999": + return defaultTimeOffset + } + + v, err := strconv.ParseInt(s, 10, 8) + if err != nil { + return defaultTimeOffset + } + + return int8(v) +} + func (m mysqlRepo) GetByID(ctx context.Context, userID model.UserID) (User, error) { u, err := m.q.Member.WithContext(ctx).Where(m.q.Member.ID.Eq(userID)).Take() if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return User{}, gerr.ErrUserNotFound } - - m.log.Error("unexpected error happened", zap.Error(err)) return User{}, errgo.Wrap(err, "dal") } @@ -60,8 +98,6 @@ func (m mysqlRepo) GetByName(ctx context.Context, username string) (User, error) if errors.Is(err, gorm.ErrRecordNotFound) { return User{}, gerr.ErrUserNotFound } - - m.log.Error("unexpected error happened", zap.Error(err)) return User{}, errgo.Wrap(err, "dal") } @@ -71,7 +107,6 @@ func (m mysqlRepo) GetByName(ctx context.Context, username string) (User, error) func (m mysqlRepo) GetByIDs(ctx context.Context, ids []model.UserID) (map[model.UserID]User, error) { u, err := m.q.Member.WithContext(ctx).Where(m.q.Member.ID.In(ids...)).Find() if err != nil { - m.log.Error("unexpected error happened", zap.Error(err)) return nil, errgo.Wrap(err, "dal") } diff --git a/main.go b/main.go index 7c046eaab..b89fe5333 100644 --- a/main.go +++ b/main.go @@ -17,11 +17,14 @@ package main import ( "fmt" + "github.com/google/uuid" + "github.com/bangumi/server/cmd" "github.com/bangumi/server/internal/pkg/logger" ) func main() { + uuid.EnableRandPool() if err := cmd.Root.Execute(); err != nil { logger.Fatal("failed to start app:\n" + fmt.Sprintf("\n%+v", err)) } diff --git a/openapi/components/episode_collection_type.yaml b/openapi/components/episode_collection_type.yaml index 1e0f71ffc..51d0c3358 100644 --- a/openapi/components/episode_collection_type.yaml +++ b/openapi/components/episode_collection_type.yaml @@ -1,6 +1,7 @@ title: EpisodeCollectionType example: 2 enum: + - 0 - 1 - 2 - 3 @@ -14,10 +15,12 @@ x-ms-enum: name: EpisodeCollectionType modelAsString: false values: + - None - Wish - Done - Dropped x-enum-varNames: + - None - Wish - Done - Dropped diff --git a/openapi/components/get-user-episodes-collection.yaml b/openapi/components/get-user-episodes-collection.yaml index df3e2afe4..cc8e580f2 100644 --- a/openapi/components/get-user-episodes-collection.yaml +++ b/openapi/components/get-user-episodes-collection.yaml @@ -1,10 +1,17 @@ -additionalProperties: false +additionalProperties: true properties: episode: $ref: "./episode.yaml" type: $ref: "./episode_collection_type.yaml" + updated_at: + type: integer + description: A int64 unix timestamp, `0` as unknown or un-recorded. + example: + - 0 + - 1700000000 required: - episode - type + - updated_at type: object diff --git a/openapi/components/subject_cat_anime.yaml b/openapi/components/subject_cat_anime.yaml new file mode 100644 index 000000000..42ca9c232 --- /dev/null +++ b/openapi/components/subject_cat_anime.yaml @@ -0,0 +1,31 @@ +title: SubjectAnimeCategory +example: 1 +enum: + - 0 + - 1 + - 2 + - 3 + - 5 +type: integer +description: |- + 动画类型 + - `0` 为 其他 + - `1` 为 TV + - `2` 为 OVA + - `3` 为 Movie + - `5` 为 WEB +x-ms-enum: + name: SubjectAnimeCategory + modelAsString: false + values: + - Other + - TV + - OVA + - Movie + - WEB +x-enum-varnames: + - Other + - TV + - OVA + - Movie + - WEB diff --git a/openapi/components/subject_cat_book.yaml b/openapi/components/subject_cat_book.yaml new file mode 100644 index 000000000..0c5cb8bd7 --- /dev/null +++ b/openapi/components/subject_cat_book.yaml @@ -0,0 +1,27 @@ +title: SubjectBookCategory +example: 1001 +enum: + - 0 + - 1001 + - 1002 + - 1003 +type: integer +description: |- + 书籍类型 + - `0` 为 其他 + - `1001` 为 漫画 + - `1002` 为 小说 + - `1003` 为 画集 +x-ms-enum: + name: SubjectBookCategory + modelAsString: false + values: + - Other + - Comic + - Novel + - Illustration +x-enum-varnames: + - Other + - Comic + - Novel + - Illustration diff --git a/openapi/components/subject_cat_game.yaml b/openapi/components/subject_cat_game.yaml new file mode 100644 index 000000000..ec1eeaafa --- /dev/null +++ b/openapi/components/subject_cat_game.yaml @@ -0,0 +1,31 @@ +title: SubjectGameCategory +example: 4001 +enum: + - 0 + - 4001 + - 4003 + - 4002 + - 4005 +type: integer +description: |- + 游戏类型 + - `0` 为 其他 + - `4001` 为 游戏 + - `4002` 为 软件 + - `4003` 为 扩展包 + - `4005` 为 桌游 +x-ms-enum: + name: SubjectGameCategory + modelAsString: false + values: + - Other + - Games + - Software + - DLC + - Tabletop +x-enum-varnames: + - Other + - Games + - Software + - DLC + - Tabletop diff --git a/openapi/components/subject_cat_real.yaml b/openapi/components/subject_cat_real.yaml new file mode 100644 index 000000000..317d4291b --- /dev/null +++ b/openapi/components/subject_cat_real.yaml @@ -0,0 +1,43 @@ +title: SubjectRealCategory +example: 6 +enum: + - 0 + - 1 + - 2 + - 3 + - 6001 + - 6002 + - 6003 + - 6004 +type: integer +description: |- + 电影类型 + - `0` 为 其他 + - `1` 为 日剧 + - `2` 为 欧美剧 + - `3` 为 华语剧 + - `6001` 为 电视剧 + - `6002` 为 电影 + - `6003` 为 演出 + - `6004` 为 综艺 +x-ms-enum: + name: SubjectRealCategory + modelAsString: false + values: + - Other + - JP + - EN + - CN + - TV + - Movie + - Live + - Show +x-enum-varnames: + - Other + - JP + - EN + - CN + - TV + - Movie + - Live + - Show diff --git a/openapi/components/subject_v0.yaml b/openapi/components/subject_v0.yaml index 7bf9df61e..d6ce9392d 100644 --- a/openapi/components/subject_v0.yaml +++ b/openapi/components/subject_v0.yaml @@ -8,8 +8,10 @@ required: - nsfw - locked - platform + - meta_tags - volumes - eps + - series - total_episodes - rating - images @@ -34,6 +36,10 @@ properties: summary: title: Summary type: string + series: + title: Series + type: boolean + description: 是否为书籍系列的主条目 nsfw: title: Nsfw type: boolean @@ -47,7 +53,7 @@ properties: platform: title: Platform type: string - description: TV, Web, 欧美剧, PS4... + description: TV, Web, 欧美剧, DLC... images: $ref: "./subject_image.yaml" infobox: @@ -132,5 +138,10 @@ properties: dropped: title: Dropped type: integer + meta_tags: + description: 由维基人维护的 tag + type: array + items: + type: string tags: $ref: "./subject_tags.yaml" diff --git a/openapi/components/subject_v0_slim.yaml b/openapi/components/subject_v0_slim.yaml index 8a47772c9..048929646 100644 --- a/openapi/components/subject_v0_slim.yaml +++ b/openapi/components/subject_v0_slim.yaml @@ -52,8 +52,12 @@ properties: type: integer score: description: 分数 - title: Total + title: Score type: number + rank: + description: 排名 + title: Rank + type: integer tags: description: 前 10 个 tag diff --git a/openapi/components/subject_collection.yaml b/openapi/components/user_subject_collection.yaml similarity index 100% rename from openapi/components/subject_collection.yaml rename to openapi/components/user_subject_collection.yaml diff --git a/openapi/components/user_subject_collection_modify_payload.yaml b/openapi/components/user_subject_collection_modify_payload.yaml new file mode 100644 index 000000000..03f4e271c --- /dev/null +++ b/openapi/components/user_subject_collection_modify_payload.yaml @@ -0,0 +1,44 @@ +title: UserSubjectCollectionModifyPayload +type: object +description: 所有的字段均可选 +properties: + type: + description: 修改条目收藏类型 + allOf: + - $ref: "./subject_collection_type.yaml" + + rate: + description: 评分,`0` 表示删除评分 + type: integer + maximum: 10 + minimum: 0 + exclusiveMaximum: false + exclusiveMinimum: false + + ep_status: + description: "只能用于修改书籍条目进度" + type: integer + minimum: 0 + exclusiveMinimum: false + + vol_status: + description: "只能用于修改书籍条目进度" + type: integer + minimum: 0 + exclusiveMinimum: false + + comment: + description: "评价" + type: string + + private: + description: "仅自己可见" + type: boolean + + tags: + title: 标签 + description: 不传或者 `null` 都会被忽略,传 `[]` 则会删除所有 tag。 + type: array + items: + type: string + description: 不能包含空格 diff --git a/openapi/index.html b/openapi/index.html index 89c1ced99..04d1deb65 100644 --- a/openapi/index.html +++ b/openapi/index.html @@ -14,7 +14,7 @@ ~ along with this program. If not, see --> - + diff --git a/openapi/readme.md b/openapi/readme.md index cddf04938..48bcf1748 100644 --- a/openapi/readme.md +++ b/openapi/readme.md @@ -6,5 +6,7 @@ 你需要 [nodejs](https://nodejs.org/) 来测试 openapi 定义: ```bash -npm test +yarn install --frozen-lockfile + +yarn run test ``` diff --git a/openapi/test.js b/openapi/test.js index a72e033a3..944727c7d 100644 --- a/openapi/test.js +++ b/openapi/test.js @@ -23,7 +23,7 @@ const colors = require("colors/safe"); async function main() { const filePath = "v0.yaml"; - console.log("try to bundle", filePath); + console.log("try to bundle", path.join(__dirname, filePath)); const openapi = await $RefParser.bundle(path.join(__dirname, filePath)); try { diff --git a/openapi/v0.yaml b/openapi/v0.yaml index d47a018fd..8134dd7c2 100644 --- a/openapi/v0.yaml +++ b/openapi/v0.yaml @@ -26,18 +26,14 @@ paths: 目前支持的筛选条件包括: - `type`: 条目类型,参照 `SubjectType` enum, `或`。 - `tag`: 标签,可以多次出现。`且` 关系。 - - `airdate`: 播出日期/发售日期。`且` 关系。 + - `air_date`: 播出日期/发售日期。`且` 关系。 - `rating`: 用于搜索指定评分的条目。`且` 关系。 + - `rating_count`: 用于按照评分人数筛选条目。`且` 关系。 - `rank`: 用于搜索指定排名的条目。`且` 关系。 - `nsfw`: 使用 `include` 包含NSFW搜索结果。默认排除搜索NSFW条目。无权限情况下忽略此选项,不会返回NSFW条目。 不同筛选条件之间为 `且` - - 由于目前 meilisearch 的一些问题,条目排名更新并不会触发搜索数据更新,所以条目排名可能是过期数据。 - - 希望未来版本的 meilisearch 能解决相关的问题。 - parameters: - name: limit in: query @@ -86,6 +82,14 @@ paths: items: $ref: "#/components/schemas/SubjectType" description: 条目类型,参照 `SubjectType` enum,多值之间为 `或` 的关系。 + meta_tags: + type: array + items: + type: string + example: + - 童年 + - 原创 + description: 公共标签。多个值之间为 `且` 关系。可以用 `-` 排除标签。比如 `-科幻` 可以排除科幻标签。 tag: type: array items: @@ -110,6 +114,14 @@ paths: - ">=6" - "<8" description: 用于搜索指定评分的条目,多值之间为 `且` 关系。 + rating_count: + type: array + items: + type: string + example: + - ">=200" + - "<5000" + description: 用于按照评分人数筛选条目,多值之间为 `且` 关系,格式与 `rating` 相同。 rank: type: array items: @@ -128,73 +140,202 @@ paths: `true` 只会返回 R18 条目。 `false` 只会返回非 R18 条目。 + responses: + 200: + description: 返回搜索结果 + content: + application/json: + schema: + "$ref": "#/components/schemas/Paged_Subject" + + "/v0/search/characters": + post: + tags: + - 角色 + summary: 角色搜索 + operationId: searchCharacters + description: | + ## 实验性 API, 本 schema 和实际的 API 行为都可能随时发生改动 + + 目前支持的筛选条件包括: + - `nsfw`: 使用 `include` 包含NSFW搜索结果。默认排除搜索NSFW条目。无权限情况下忽略此选项,不会返回NSFW条目。 + + parameters: + - name: limit + in: query + description: 分页参数 + required: false + schema: + type: integer + - name: offset + in: query + description: 分页参数 + required: false + schema: + type: integer + requestBody: + content: + "application/json": + schema: + type: object + required: + - keyword + properties: + keyword: + type: string + filter: + type: object + description: 不同条件之间是 `且` 的关系 + properties: + nsfw: + type: boolean + description: | + 无权限的用户会直接忽略此字段,不会返回 R18 角色。 + + 默认或者 `null` 会返回包含 R18 的所有搜索结果。 + + `true` 只会返回 R18 角色。 + `false` 只会返回非 R18 角色。 responses: 200: description: 返回搜索结果 content: application/json: schema: - description: 用户信息 - type: object - properties: - total: - description: 搜索结果数量 - type: integer - example: 100 - limit: - description: 当前分页参数 - type: integer - example: 100 - offset: - description: 当前分页参数 - type: integer - example: 100 - data: - type: array - items: - type: object - required: - - score - - id - - rank - - tags - - name - - name_cn - - image - - date - - summary - properties: - id: - description: 条目ID - type: integer - example: 8 - type: - $ref: "#/components/schemas/SubjectType" - "date": - "type": "string" - description: 上映/开播/连载开始日期,可能为空字符串 - "image": - "type": "string" - format: url - description: 封面 - summary: - type: string - description: 条目描述 - "name": - "type": "string" - description: 条目原名 - "name_cn": - "type": "string" - description: 条目中文名 - "tags": - $ref: "#/components/schemas/SubjectTags" - "score": - description: 评分 - "type": "number" - "rank": - description: 排名 - "type": "integer" + "$ref": "#/components/schemas/Paged_Character" + + "/v0/search/persons": + post: + tags: + - 人物 + summary: 人物搜索 + operationId: searchPersons + description: | + ## 实验性 API, 本 schema 和实际的 API 行为都可能随时发生改动 + + 目前支持的筛选条件包括: + - `career`: 职业,可以多次出现。`且` 关系。 + + 不同筛选条件之间为 `且` + + parameters: + - name: limit + in: query + description: 分页参数 + required: false + schema: + type: integer + - name: offset + in: query + description: 分页参数 + required: false + schema: + type: integer + requestBody: + content: + "application/json": + schema: + type: object + required: + - keyword + properties: + keyword: + type: string + filter: + type: object + description: 不同条件之间是 `且` 的关系 + properties: + career: + type: array + items: + type: string + example: + - artist + - director + description: 职业,可以多次出现。多值之间为 `且` 关系。 + responses: + 200: + description: 返回搜索结果 + content: + application/json: + schema: + "$ref": "#/components/schemas/Paged_Person" + + "/v0/subjects": + get: + tags: + - 条目 + summary: 浏览条目 + description: 第一页会 cache 24h,之后会 cache 1h + operationId: getSubjects + parameters: + - name: type + in: query + description: 条目类型 + required: true + schema: + $ref: "#/components/schemas/SubjectType" + - name: cat + in: query + description: 条目分类,参照 `SubjectCategory` enum + required: false + schema: + $ref: "#/components/schemas/SubjectCategory" + - name: series + in: query + description: 是否系列,仅对书籍类型的条目有效 + required: false + schema: + type: boolean + - name: platform + in: query + description: 平台,仅对游戏类型的条目有效 + required: false + schema: + type: string + - name: sort + in: query + description: 排序,枚举值 {date|rank} + required: false + schema: + title: Sort Order + type: string + - name: year + in: query + description: 年份 + required: false + schema: + type: integer + - name: month + in: query + description: 月份 + required: false + schema: + type: integer + - $ref: "#/components/parameters/default_query_limit" + - $ref: "#/components/parameters/default_query_offset" + responses: + "200": + description: Successful Response + content: + application/json: + schema: + "$ref": "#/components/schemas/Paged_Subject" + "400": + description: Validation Error + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + "404": + description: Not Found + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + security: + - OptionalHTTPBearer: [] "/v0/subjects/{subject_id}": get: @@ -373,7 +514,6 @@ paths: allOf: - "$ref": "#/components/schemas/EpType" description: 参照章节的`type` - type: integer name: type in: query - required: false @@ -460,7 +600,7 @@ paths: content: application/json: schema: - "$ref": "#/components/schemas/CharacterDetail" + "$ref": "#/components/schemas/Character" "404": description: Not Found content: @@ -571,6 +711,70 @@ paths: application/json: schema: "$ref": "#/components/schemas/ErrorDetail" + "/v0/characters/{character_id}/collect": + post: + tags: + - 角色 + summary: Collect character for current user + operationId: collectCharacterByCharacterIdAndUserId + description: 为当前用户收藏角色 + parameters: + - $ref: "#/components/parameters/path_character_id" + responses: + "204": + description: Successful Response + "400": + description: character ID not valid + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + "401": + description: not authorized + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + "404": + description: 角色不存在 + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + security: + - HTTPBearer: [] + delete: + tags: + - 角色 + summary: Uncollect character for current user + operationId: uncollectCharacterByCharacterIdAndUserId + description: 为当前用户取消收藏角色 + parameters: + - $ref: "#/components/parameters/path_character_id" + responses: + "204": + description: Successful Response + "400": + description: character ID not valid + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + "401": + description: not authorized + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + "404": + description: 角色不存在 + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + security: + - HTTPBearer: [] + "/v0/persons/{person_id}": get: tags: @@ -602,7 +806,7 @@ paths: "/v0/persons/{person_id}/image": get: tags: - - 角色 + - 人物 summary: Get Person Image operationId: getPersonImageById parameters: @@ -696,6 +900,69 @@ paths: application/json: schema: "$ref": "#/components/schemas/ErrorDetail" + "/v0/persons/{person_id}/collect": + post: + tags: + - 人物 + summary: Collect person for current user + operationId: collectPersonByPersonIdAndUserId + description: 为当前用户收藏人物 + parameters: + - $ref: "#/components/parameters/path_person_id" + responses: + "204": + description: Successful Response + "400": + description: person ID not valid + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + "401": + description: not authorized + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + "404": + description: 人物不存在 + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + security: + - OptionalHTTPBearer: [] + delete: + tags: + - 人物 + summary: Uncollect person for current user + operationId: uncollectPersonByPersonIdAndUserId + description: 为当前用户取消收藏人物 + parameters: + - $ref: "#/components/parameters/path_person_id" + responses: + "204": + description: Successful Response + "400": + description: person ID not valid + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + "401": + description: not authorized + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + "404": + description: 人物不存在 + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + security: + - OptionalHTTPBearer: [] "/v0/users/{username}": get: @@ -780,8 +1047,25 @@ paths: content: application/json: schema: - "$ref": "#/components/schemas/User" - "403": + allOf: + - "$ref": "#/components/schemas/User" + - required: + - email + - reg_time + - type: object + properties: + email: + description: "用户绑定的邮箱地址" + type: string + format: email + reg_time: + description: "用户注册时间。比如 2017-12-03T08:51:16+08:00" + type: string + format: date-time + time_offset: + description: "用户设置的时区偏移,以小时为单位。比如 GMT+8(shanghai/beijing)为 8" + type: integer + "401": description: unauthorized content: application/json: @@ -816,7 +1100,6 @@ paths: schema: allOf: - "$ref": "#/components/schemas/SubjectCollectionType" - type: integer name: type in: query - $ref: "#/components/parameters/default_query_limit" @@ -847,8 +1130,8 @@ paths: get: tags: - 收藏 - summary: 获取用户单个收藏 - description: 获取对应用户的收藏,查看私有收藏需要access token。 + summary: 获取用户单个条目收藏 + description: 获取对应用户的收藏,查看私有收藏需要 access token operationId: getUserCollection parameters: - $ref: "#/components/parameters/path_username" @@ -875,68 +1158,65 @@ paths: security: - OptionalHTTPBearer: [] "/v0/users/-/collections/{subject_id}": - patch: + post: tags: - 收藏 - summary: 修改用户单个收藏 + summary: 新增或修改用户单个条目收藏 description: | - 修改条目收藏状态 + 修改条目收藏状态, 如果不存在则创建,如果存在则修改 由于直接修改剧集条目的完成度可能会引起意料之外效果,只能用于修改书籍类条目的完成度。 - PATCH 方法的所有请求体字段均可选 - operationId: patchUserCollection + 方法的所有请求体字段均可选 + operationId: postUserCollection parameters: - $ref: "#/components/parameters/path_subject_id" requestBody: content: application/json: schema: - # SubjectEpisodeCollectionPatch - type: object - description: 所有的字段均可选 - properties: - type: - description: 修改条目收藏类型 - allOf: - - $ref: "#/components/schemas/SubjectCollectionType" - - rate: - description: 评分,`0` 表示删除评分 - type: integer - maximum: 10 - minimum: 0 - exclusiveMaximum: false - exclusiveMinimum: false - - ep_status: - description: "只能用于修改书籍条目进度" - type: integer - minimum: 0 - exclusiveMinimum: false - - vol_status: - description: "只能用于修改书籍条目进度" - type: integer - minimum: 0 - exclusiveMinimum: false - - comment: - description: "评价" - type: string - - private: - description: "仅自己可见" - type: boolean + "$ref": "#/components/schemas/UserSubjectCollectionModifyPayload" + responses: + "204": + description: Successful Response + "400": + description: Validation Error + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + "401": + description: Unauthorized + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + "404": + description: 用户不存在 + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + security: + - OptionalHTTPBearer: [] + patch: + tags: + - 收藏 + summary: 修改用户单个收藏 + description: | + 修改条目收藏状态 - tags: - title: 标签 - description: 不传或者 `null` 都会被忽略,传 `[]` 则会删除所有 tag。 - type: array - items: - type: string - description: 不能包含空格 + 由于直接修改剧集条目的完成度可能会引起意料之外效果,只能用于修改书籍类条目的完成度。 + PATCH 方法的所有请求体字段均可选 + operationId: patchUserCollection + parameters: + - $ref: "#/components/parameters/path_subject_id" + requestBody: + content: + application/json: + schema: + "$ref": "#/components/schemas/UserSubjectCollectionModifyPayload" responses: "204": description: Successful Response @@ -1146,6 +1426,106 @@ paths: security: - HTTPBearer: [] + "/v0/users/{username}/collections/-/characters": + get: + tags: + - 收藏 + summary: 获取用户角色收藏列表 + operationId: getUserCharacterCollections + parameters: + - $ref: "#/components/parameters/path_username" + responses: + "200": + description: Successful Response + content: + application/json: + schema: + "$ref": "#/components/schemas/Paged_UserCharacterCollection" + "404": + description: 用户不存在 + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + "/v0/users/{username}/collections/-/characters/{character_id}": + get: + tags: + - 收藏 + summary: 获取用户单个角色收藏信息 + operationId: getUserCharacterCollection + parameters: + - $ref: "#/components/parameters/path_username" + - $ref: "#/components/parameters/path_character_id" + responses: + "200": + description: Successful Response + content: + application/json: + schema: + "$ref": "#/components/schemas/UserCharacterCollection" + "400": + description: character ID not valid + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + "404": + description: 用户或角色不存在 + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + + "/v0/users/{username}/collections/-/persons": + get: + tags: + - 收藏 + summary: 获取用户人物收藏列表 + operationId: getUserPersonCollections + parameters: + - $ref: "#/components/parameters/path_username" + responses: + "200": + description: Successful Response + content: + application/json: + schema: + "$ref": "#/components/schemas/Paged_UserPersonCollection" + "404": + description: 用户不存在 + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + "/v0/users/{username}/collections/-/persons/{person_id}": + get: + tags: + - 收藏 + summary: 获取用户单个人物收藏信息 + operationId: getUserPersonCollection + parameters: + - $ref: "#/components/parameters/path_username" + - $ref: "#/components/parameters/path_person_id" + responses: + "200": + description: Successful Response + content: + application/json: + schema: + "$ref": "#/components/schemas/UserPersonCollection" + "400": + description: person ID not valid + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + "404": + description: 用户或人物不存在 + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorDetail" + "/v0/revisions/persons": get: tags: @@ -1713,8 +2093,8 @@ components: - B - AB - O - CharacterDetail: - title: CharacterDetail + Character: + title: Character required: - id - name @@ -1785,6 +2165,7 @@ components: - name - type - subject_id + - subject_type - subject_name - subject_name_cn type: object @@ -1809,6 +2190,8 @@ components: subject_id: title: Subject ID type: integer + subject_type: + $ref: "#/components/schemas/SubjectType" subject_name: title: Subject Name type: string @@ -2083,9 +2466,7 @@ components: title: ID type: integer type: - title: Type - type: integer - description: "`0` 本篇,`1` SP,`2` OP,`3` ED" + $ref: "#/components/schemas/EpType" name: title: Name type: string @@ -2170,7 +2551,9 @@ components: "$ref": "#/components/schemas/Creator" ban: title: Ban + deprecated: true type: boolean + description: deprecated, always false. nsfw: title: 目录是否包括 nsfw 条目 type: boolean @@ -2252,6 +2635,72 @@ components: Page: $ref: "./components/page.yaml" + Paged_Subject: + title: Paged[Subject] + type: object + properties: + total: + title: Total + type: integer + default: 0 + limit: + title: Limit + type: integer + default: 0 + offset: + title: Offset + type: integer + default: 0 + data: + title: Data + type: array + items: + "$ref": "#/components/schemas/Subject" + default: [] + Paged_Character: + title: Paged[Character] + type: object + properties: + total: + title: Total + type: integer + default: 0 + limit: + title: Limit + type: integer + default: 0 + offset: + title: Offset + type: integer + default: 0 + data: + title: Data + type: array + items: + "$ref": "#/components/schemas/Character" + default: [] + Paged_Person: + title: Paged[Person] + type: object + properties: + total: + title: Total + type: integer + default: 0 + limit: + title: Limit + type: integer + default: 0 + offset: + title: Offset + type: integer + default: 0 + data: + title: Data + type: array + items: + "$ref": "#/components/schemas/Person" + default: [] Paged_Episode: title: Paged[Episode] type: object @@ -2340,6 +2789,50 @@ components: items: "$ref": "#/components/schemas/UserSubjectCollection" default: [] + Paged_UserCharacterCollection: + title: Paged[UserCharacterCollection] + type: object + properties: + total: + title: Total + type: integer + default: 0 + limit: + title: Limit + type: integer + default: 0 + offset: + title: Offset + type: integer + default: 0 + data: + title: Data + type: array + items: + "$ref": "#/components/schemas/UserCharacterCollection" + default: [] + Paged_UserPersonCollection: + title: Paged[UserPersonCollection] + type: object + properties: + total: + title: Total + type: integer + default: 0 + limit: + title: Limit + type: integer + default: 0 + offset: + title: Offset + type: integer + default: 0 + data: + title: Data + type: array + items: + "$ref": "#/components/schemas/UserPersonCollection" + default: [] Person: title: Person required: @@ -2397,6 +2890,7 @@ components: - name - type - subject_id + - subject_type - subject_name - subject_name_cn type: object @@ -2420,6 +2914,8 @@ components: subject_id: title: Subject ID type: integer + subject_type: + $ref: "#/components/schemas/SubjectType" subject_name: title: Subject Name type: string @@ -2551,6 +3047,7 @@ components: required: - id - name + - summary - type - relation type: object @@ -2561,6 +3058,9 @@ components: name: title: Name type: string + summary: + title: Summary + type: string type: type: integer allOf: @@ -2590,6 +3090,7 @@ components: - type - career - relation + - eps type: object properties: id: @@ -2616,6 +3117,75 @@ components: relation: title: Relation type: string + eps: + title: Eps + type: string + description: 参与章节/曲目 + UserCharacterCollection: + title: UserCharacterCollection + required: + - id + - name + - type + - created_at + type: object + properties: + id: + title: ID + type: integer + name: + title: Name + type: string + type: + type: integer + allOf: + - "$ref": "#/components/schemas/CharacterType" + description: 角色,机体,舰船,组织... + images: + title: Images + type: object + allOf: + - "$ref": "#/components/schemas/PersonImages" + description: object with some size of images, this object maybe `null` + created_at: + title: Created At + type: string + format: date-time + UserPersonCollection: + title: UserPersonCollection + required: + - id + - name + - type + - career + - created_at + type: object + properties: + id: + title: ID + type: integer + name: + title: Name + type: string + type: + type: integer + allOf: + - "$ref": "#/components/schemas/PersonType" + description: "`1`, `2`, `3` 表示 `个人`, `公司`, `组合`" + career: + type: array + items: + "$ref": "#/components/schemas/PersonCareer" + images: + title: Images + type: object + allOf: + - "$ref": "#/components/schemas/PersonImages" + description: object with some size of images, this object maybe `null` + created_at: + title: Created At + type: string + format: date-time Revision: title: Revision required: @@ -2661,24 +3231,49 @@ components: $ref: "./components/subject_tags.yaml" SubjectType: $ref: "./components/subject_type.yaml" + SubjectBookCategory: + $ref: "./components/subject_cat_book.yaml" + SubjectAnimeCategory: + $ref: "./components/subject_cat_anime.yaml" + SubjectGameCategory: + $ref: "./components/subject_cat_game.yaml" + SubjectRealCategory: + $ref: "./components/subject_cat_real.yaml" + SubjectCategory: + anyOf: + - $ref: "#/components/schemas/SubjectBookCategory" + - $ref: "#/components/schemas/SubjectAnimeCategory" + - $ref: "#/components/schemas/SubjectGameCategory" + - $ref: "#/components/schemas/SubjectRealCategory" UserSubjectCollection: - $ref: "./components/subject_collection.yaml" + $ref: "./components/user_subject_collection.yaml" + UserSubjectCollectionModifyPayload: + $ref: "./components/user_subject_collection_modify_payload.yaml" UserEpisodeCollection: $ref: "./components/get-user-episodes-collection.yaml" v0_RelatedSubject: title: RelatedSubject required: - id + - type - staff + - eps + - name - name_cn type: object properties: id: title: ID type: integer + type: + $ref: "#/components/schemas/SubjectType" staff: title: Staff type: string + eps: + title: Eps + type: string + description: 参与章节/曲目 name: title: Name type: string diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index f384fb605..000000000 --- a/package-lock.json +++ /dev/null @@ -1,3515 +0,0 @@ -{ - "name": "chii", - "version": "0.32.8", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "chii", - "version": "0.32.8", - "dependencies": { - "@apidevtools/json-schema-ref-parser": "^10.1.0", - "conventional-changelog": "^3.1.25", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21" - }, - "devDependencies": { - "@types/conventional-changelog": "^3.1.1", - "colors": "^1.4.0", - "oas-validator": "^5.0.8", - "prettier": "^2.8.7" - } - }, - "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-10.1.0.tgz", - "integrity": "sha512-3e+viyMuXdrcK8v5pvP+SDoAQ77FH6OyRmuK48SZKmdHJRFm87RsSs8qm6kP39a/pOPURByJw+OXzQIqcfmKtA==", - "dependencies": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.11", - "@types/lodash.clonedeep": "^4.5.7", - "js-yaml": "^4.1.0", - "lodash.clonedeep": "^4.5.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/philsturgeon" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dependencies": { - "@babel/highlight": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz", - "integrity": "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@exodus/schemasafe": { - "version": "1.0.0-rc.6", - "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.0.0-rc.6.tgz", - "integrity": "sha512-dDnQizD94EdBwEj/fh3zPRa/HWCS9O5au2PuHhZBbuM3xWHxuaKzPBOEWze7Nn0xW68MIpZ7Xdyn1CoCpjKCuQ==", - "dev": true - }, - "node_modules/@hutson/parse-repository-url": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz", - "integrity": "sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@jsdevtools/ono": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", - "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" - }, - "node_modules/@types/conventional-changelog": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/conventional-changelog/-/conventional-changelog-3.1.1.tgz", - "integrity": "sha512-+Ei7ZUTHnWseUthn22+MVLQjcQZ0AitqOk/UMl3/3aX5FMPofGeKVn/0HZYabsLN+kCioP3FpBrucrWdcCoMJw==", - "dev": true, - "dependencies": { - "@types/conventional-changelog-core": "*", - "@types/conventional-changelog-writer": "*", - "@types/conventional-commits-parser": "*", - "@types/node": "*" - } - }, - "node_modules/@types/conventional-changelog-core": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@types/conventional-changelog-core/-/conventional-changelog-core-4.2.1.tgz", - "integrity": "sha512-XdQKXrWvHYvnhuXal4JcvIh3sn9LpcER/WjWDDQHh0yz3/yt0r8TvE5AyJwX6xL9BwJRpE1Ce+A0Hj16nsfI6w==", - "dev": true, - "dependencies": { - "@types/conventional-changelog-writer": "*", - "@types/conventional-commits-parser": "*", - "@types/conventional-recommended-bump": "*", - "@types/git-raw-commits": "*", - "@types/node": "*", - "@types/normalize-package-data": "*" - } - }, - "node_modules/@types/conventional-changelog-writer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/conventional-changelog-writer/-/conventional-changelog-writer-4.0.1.tgz", - "integrity": "sha512-S7lJJByPMkkocMWnDKOtkSLi9yXu619+GhGejPnCiNK1Dgwjf5jjzBxXYgMv47tBG8MokmCCV1sWhI53lFl6FA==", - "dev": true, - "dependencies": { - "@types/conventional-commits-parser": "*", - "@types/node": "*" - } - }, - "node_modules/@types/conventional-commits-parser": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-3.0.2.tgz", - "integrity": "sha512-1kVPUHFaart1iGRFxKn8WNXYEDVAgMb+DLatgql2dGg9jTGf3bNxWtN//C/tDG3ckOLg4u7SSx+qcn8VjzI5zg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/conventional-recommended-bump": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/conventional-recommended-bump/-/conventional-recommended-bump-6.1.0.tgz", - "integrity": "sha512-AlhJsbzY2W+9xkn44qvEgeJGY6FbQIluPRZawwU2JgJw+o7PYB/yjeuRsJhNZRKto3j71hDj+hQik10w/7bI4Q==", - "dev": true, - "dependencies": { - "@types/conventional-changelog-core": "*", - "@types/conventional-changelog-writer": "*", - "@types/conventional-commits-parser": "*" - } - }, - "node_modules/@types/git-raw-commits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/git-raw-commits/-/git-raw-commits-2.0.1.tgz", - "integrity": "sha512-vE2lbXxqJ0AqMDoP4N6d+WVfbcBla9+z8IL6e+37JNQIwYZCYY0z3J7hdpY8D/VGwFZ0yIYQLcqk8eCnfXsaEg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" - }, - "node_modules/@types/lodash": { - "version": "4.14.191", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz", - "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==" - }, - "node_modules/@types/lodash.clonedeep": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.7.tgz", - "integrity": "sha512-ccNqkPptFIXrpVqUECi60/DFxjNKsfoQxSQsgcBJCX/fuX1wgyQieojkcWH/KpE3xzLoWN/2k+ZeGqIN3paSvw==", - "dependencies": { - "@types/lodash": "*" - } - }, - "node_modules/@types/minimist": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", - "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==" - }, - "node_modules/@types/node": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.35.tgz", - "integrity": "sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg==", - "dev": true - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==" - }, - "node_modules/add-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", - "integrity": "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==" - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "node_modules/array-ify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==" - }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", - "dev": true - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/chalk/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/compare-func": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", - "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", - "dependencies": { - "array-ify": "^1.0.0", - "dot-prop": "^5.1.0" - } - }, - "node_modules/conventional-changelog": { - "version": "3.1.25", - "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-3.1.25.tgz", - "integrity": "sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ==", - "dependencies": { - "conventional-changelog-angular": "^5.0.12", - "conventional-changelog-atom": "^2.0.8", - "conventional-changelog-codemirror": "^2.0.8", - "conventional-changelog-conventionalcommits": "^4.5.0", - "conventional-changelog-core": "^4.2.1", - "conventional-changelog-ember": "^2.0.9", - "conventional-changelog-eslint": "^3.0.9", - "conventional-changelog-express": "^2.0.6", - "conventional-changelog-jquery": "^3.0.11", - "conventional-changelog-jshint": "^2.0.9", - "conventional-changelog-preset-loader": "^2.3.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-angular": { - "version": "5.0.13", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz", - "integrity": "sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==", - "dependencies": { - "compare-func": "^2.0.0", - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-atom": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-2.0.8.tgz", - "integrity": "sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==", - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-codemirror": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-2.0.8.tgz", - "integrity": "sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==", - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-conventionalcommits": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.6.3.tgz", - "integrity": "sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==", - "dependencies": { - "compare-func": "^2.0.0", - "lodash": "^4.17.15", - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-core": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.2.4.tgz", - "integrity": "sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==", - "dependencies": { - "add-stream": "^1.0.0", - "conventional-changelog-writer": "^5.0.0", - "conventional-commits-parser": "^3.2.0", - "dateformat": "^3.0.0", - "get-pkg-repo": "^4.0.0", - "git-raw-commits": "^2.0.8", - "git-remote-origin-url": "^2.0.0", - "git-semver-tags": "^4.1.1", - "lodash": "^4.17.15", - "normalize-package-data": "^3.0.0", - "q": "^1.5.1", - "read-pkg": "^3.0.0", - "read-pkg-up": "^3.0.0", - "through2": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-ember": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-2.0.9.tgz", - "integrity": "sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==", - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-eslint": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-3.0.9.tgz", - "integrity": "sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==", - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-express": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-2.0.6.tgz", - "integrity": "sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==", - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-jquery": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-3.0.11.tgz", - "integrity": "sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==", - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-jshint": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.9.tgz", - "integrity": "sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==", - "dependencies": { - "compare-func": "^2.0.0", - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-preset-loader": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz", - "integrity": "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==", - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-writer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-5.0.1.tgz", - "integrity": "sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==", - "dependencies": { - "conventional-commits-filter": "^2.0.7", - "dateformat": "^3.0.0", - "handlebars": "^4.7.7", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "semver": "^6.0.0", - "split": "^1.0.0", - "through2": "^4.0.0" - }, - "bin": { - "conventional-changelog-writer": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-commits-filter": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz", - "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==", - "dependencies": { - "lodash.ismatch": "^4.4.0", - "modify-values": "^1.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-commits-parser": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz", - "integrity": "sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==", - "dependencies": { - "is-text-path": "^1.0.1", - "JSONStream": "^1.0.4", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - }, - "bin": { - "conventional-commits-parser": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "node_modules/dargs": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", - "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", - "engines": { - "node": "*" - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "dev": true - }, - "node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-pkg-repo": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz", - "integrity": "sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==", - "dependencies": { - "@hutson/parse-repository-url": "^3.0.0", - "hosted-git-info": "^4.0.0", - "through2": "^2.0.0", - "yargs": "^16.2.0" - }, - "bin": { - "get-pkg-repo": "src/cli.js" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-pkg-repo/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/get-pkg-repo/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/get-pkg-repo/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/get-pkg-repo/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/git-raw-commits": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz", - "integrity": "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==", - "dependencies": { - "dargs": "^7.0.0", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - }, - "bin": { - "git-raw-commits": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/git-remote-origin-url": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", - "integrity": "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=", - "dependencies": { - "gitconfiglocal": "^1.0.0", - "pify": "^2.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/git-semver-tags": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-4.1.1.tgz", - "integrity": "sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==", - "dependencies": { - "meow": "^8.0.0", - "semver": "^6.0.0" - }, - "bin": { - "git-semver-tags": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/gitconfiglocal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", - "integrity": "sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=", - "dependencies": { - "ini": "^1.3.2" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "node_modules/handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "engines": { - "node": ">=4" - } - }, - "node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/http2-client": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", - "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", - "dev": true - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "node_modules/is-core-module": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", - "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-text-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", - "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", - "dependencies": { - "text-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "engines": [ - "node >= 0.2.0" - ] - }, - "node_modules/JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dependencies": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "bin.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/load-json-file/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "engines": { - "node": ">=4" - } - }, - "node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" - }, - "node_modules/lodash.ismatch": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", - "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=" - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", - "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/meow/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" - }, - "node_modules/meow/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/meow/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/meow/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/meow/node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/meow/node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/meow/node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/meow/node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/meow/node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/meow/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "engines": { - "node": ">=4" - } - }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, - "node_modules/minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/modify-values": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", - "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, - "node_modules/node-fetch-h2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz", - "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", - "dev": true, - "dependencies": { - "http2-client": "^1.2.5" - }, - "engines": { - "node": "4.x || >=6.0.0" - } - }, - "node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/oas-kit-common": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", - "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==", - "dev": true, - "dependencies": { - "fast-safe-stringify": "^2.0.7" - } - }, - "node_modules/oas-linter": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz", - "integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==", - "dev": true, - "dependencies": { - "@exodus/schemasafe": "^1.0.0-rc.2", - "should": "^13.2.1", - "yaml": "^1.10.0" - }, - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" - } - }, - "node_modules/oas-resolver": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz", - "integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==", - "dev": true, - "dependencies": { - "node-fetch-h2": "^2.3.0", - "oas-kit-common": "^1.0.8", - "reftools": "^1.1.9", - "yaml": "^1.10.0", - "yargs": "^17.0.1" - }, - "bin": { - "resolve": "resolve.js" - }, - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" - } - }, - "node_modules/oas-resolver/node_modules/yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/oas-resolver/node_modules/yargs-parser": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", - "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/oas-schema-walker": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz", - "integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==", - "dev": true, - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" - } - }, - "node_modules/oas-validator": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz", - "integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==", - "dev": true, - "dependencies": { - "call-me-maybe": "^1.0.1", - "oas-kit-common": "^1.0.8", - "oas-linter": "^3.2.2", - "oas-resolver": "^2.5.6", - "oas-schema-walker": "^1.1.5", - "reftools": "^1.1.9", - "should": "^13.2.1", - "yaml": "^1.10.0" - }, - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" - } - }, - "node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "engines": { - "node": ">=4" - } - }, - "node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "engines": { - "node": ">=4" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-type/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "engines": { - "node": ">=4" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prettier": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", - "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, - "node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dependencies": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "dependencies": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" - }, - "node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/read-pkg/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/reftools": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz", - "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==", - "dev": true, - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", - "dependencies": { - "is-core-module": "^2.8.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/should": { - "version": "13.2.3", - "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", - "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", - "dev": true, - "dependencies": { - "should-equal": "^2.0.0", - "should-format": "^3.0.3", - "should-type": "^1.4.0", - "should-type-adaptors": "^1.0.1", - "should-util": "^1.0.0" - } - }, - "node_modules/should-equal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", - "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", - "dev": true, - "dependencies": { - "should-type": "^1.4.0" - } - }, - "node_modules/should-format": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", - "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", - "dev": true, - "dependencies": { - "should-type": "^1.3.0", - "should-type-adaptors": "^1.0.1" - } - }, - "node_modules/should-type": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", - "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==", - "dev": true - }, - "node_modules/should-type-adaptors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", - "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", - "dev": true, - "dependencies": { - "should-type": "^1.3.0", - "should-util": "^1.0.0" - } - }, - "node_modules/should-util": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", - "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", - "dev": true - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", - "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==" - }, - "node_modules/split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", - "dependencies": { - "readable-stream": "^3.0.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/text-extensions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", - "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "node_modules/through2": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", - "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", - "dependencies": { - "readable-stream": "3" - } - }, - "node_modules/trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/uglify-js": { - "version": "3.15.5", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.5.tgz", - "integrity": "sha512-hNM5q5GbBRB5xB+PMqVRcgYe4c8jbyZ1pzZhS6jbq54/4F2gFK869ZheiE5A8/t+W5jtTNpWef/5Q9zk639FNQ==", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "engines": { - "node": ">=10" - } - } - }, - "dependencies": { - "@apidevtools/json-schema-ref-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-10.1.0.tgz", - "integrity": "sha512-3e+viyMuXdrcK8v5pvP+SDoAQ77FH6OyRmuK48SZKmdHJRFm87RsSs8qm6kP39a/pOPURByJw+OXzQIqcfmKtA==", - "requires": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.11", - "@types/lodash.clonedeep": "^4.5.7", - "js-yaml": "^4.1.0", - "lodash.clonedeep": "^4.5.0" - } - }, - "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "requires": { - "@babel/highlight": "^7.16.7" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==" - }, - "@babel/highlight": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz", - "integrity": "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==", - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@exodus/schemasafe": { - "version": "1.0.0-rc.6", - "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.0.0-rc.6.tgz", - "integrity": "sha512-dDnQizD94EdBwEj/fh3zPRa/HWCS9O5au2PuHhZBbuM3xWHxuaKzPBOEWze7Nn0xW68MIpZ7Xdyn1CoCpjKCuQ==", - "dev": true - }, - "@hutson/parse-repository-url": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz", - "integrity": "sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==" - }, - "@jsdevtools/ono": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", - "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" - }, - "@types/conventional-changelog": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/conventional-changelog/-/conventional-changelog-3.1.1.tgz", - "integrity": "sha512-+Ei7ZUTHnWseUthn22+MVLQjcQZ0AitqOk/UMl3/3aX5FMPofGeKVn/0HZYabsLN+kCioP3FpBrucrWdcCoMJw==", - "dev": true, - "requires": { - "@types/conventional-changelog-core": "*", - "@types/conventional-changelog-writer": "*", - "@types/conventional-commits-parser": "*", - "@types/node": "*" - } - }, - "@types/conventional-changelog-core": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@types/conventional-changelog-core/-/conventional-changelog-core-4.2.1.tgz", - "integrity": "sha512-XdQKXrWvHYvnhuXal4JcvIh3sn9LpcER/WjWDDQHh0yz3/yt0r8TvE5AyJwX6xL9BwJRpE1Ce+A0Hj16nsfI6w==", - "dev": true, - "requires": { - "@types/conventional-changelog-writer": "*", - "@types/conventional-commits-parser": "*", - "@types/conventional-recommended-bump": "*", - "@types/git-raw-commits": "*", - "@types/node": "*", - "@types/normalize-package-data": "*" - } - }, - "@types/conventional-changelog-writer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/conventional-changelog-writer/-/conventional-changelog-writer-4.0.1.tgz", - "integrity": "sha512-S7lJJByPMkkocMWnDKOtkSLi9yXu619+GhGejPnCiNK1Dgwjf5jjzBxXYgMv47tBG8MokmCCV1sWhI53lFl6FA==", - "dev": true, - "requires": { - "@types/conventional-commits-parser": "*", - "@types/node": "*" - } - }, - "@types/conventional-commits-parser": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-3.0.2.tgz", - "integrity": "sha512-1kVPUHFaart1iGRFxKn8WNXYEDVAgMb+DLatgql2dGg9jTGf3bNxWtN//C/tDG3ckOLg4u7SSx+qcn8VjzI5zg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/conventional-recommended-bump": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/conventional-recommended-bump/-/conventional-recommended-bump-6.1.0.tgz", - "integrity": "sha512-AlhJsbzY2W+9xkn44qvEgeJGY6FbQIluPRZawwU2JgJw+o7PYB/yjeuRsJhNZRKto3j71hDj+hQik10w/7bI4Q==", - "dev": true, - "requires": { - "@types/conventional-changelog-core": "*", - "@types/conventional-changelog-writer": "*", - "@types/conventional-commits-parser": "*" - } - }, - "@types/git-raw-commits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/git-raw-commits/-/git-raw-commits-2.0.1.tgz", - "integrity": "sha512-vE2lbXxqJ0AqMDoP4N6d+WVfbcBla9+z8IL6e+37JNQIwYZCYY0z3J7hdpY8D/VGwFZ0yIYQLcqk8eCnfXsaEg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" - }, - "@types/lodash": { - "version": "4.14.191", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz", - "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==" - }, - "@types/lodash.clonedeep": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.7.tgz", - "integrity": "sha512-ccNqkPptFIXrpVqUECi60/DFxjNKsfoQxSQsgcBJCX/fuX1wgyQieojkcWH/KpE3xzLoWN/2k+ZeGqIN3paSvw==", - "requires": { - "@types/lodash": "*" - } - }, - "@types/minimist": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", - "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==" - }, - "@types/node": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.35.tgz", - "integrity": "sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg==", - "dev": true - }, - "@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==" - }, - "add-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", - "integrity": "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==" - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "array-ify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==" - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==" - }, - "call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - }, - "camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "requires": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - } - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true - }, - "compare-func": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", - "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", - "requires": { - "array-ify": "^1.0.0", - "dot-prop": "^5.1.0" - } - }, - "conventional-changelog": { - "version": "3.1.25", - "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-3.1.25.tgz", - "integrity": "sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ==", - "requires": { - "conventional-changelog-angular": "^5.0.12", - "conventional-changelog-atom": "^2.0.8", - "conventional-changelog-codemirror": "^2.0.8", - "conventional-changelog-conventionalcommits": "^4.5.0", - "conventional-changelog-core": "^4.2.1", - "conventional-changelog-ember": "^2.0.9", - "conventional-changelog-eslint": "^3.0.9", - "conventional-changelog-express": "^2.0.6", - "conventional-changelog-jquery": "^3.0.11", - "conventional-changelog-jshint": "^2.0.9", - "conventional-changelog-preset-loader": "^2.3.4" - } - }, - "conventional-changelog-angular": { - "version": "5.0.13", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz", - "integrity": "sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==", - "requires": { - "compare-func": "^2.0.0", - "q": "^1.5.1" - } - }, - "conventional-changelog-atom": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-2.0.8.tgz", - "integrity": "sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==", - "requires": { - "q": "^1.5.1" - } - }, - "conventional-changelog-codemirror": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-2.0.8.tgz", - "integrity": "sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==", - "requires": { - "q": "^1.5.1" - } - }, - "conventional-changelog-conventionalcommits": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.6.3.tgz", - "integrity": "sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==", - "requires": { - "compare-func": "^2.0.0", - "lodash": "^4.17.15", - "q": "^1.5.1" - } - }, - "conventional-changelog-core": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.2.4.tgz", - "integrity": "sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==", - "requires": { - "add-stream": "^1.0.0", - "conventional-changelog-writer": "^5.0.0", - "conventional-commits-parser": "^3.2.0", - "dateformat": "^3.0.0", - "get-pkg-repo": "^4.0.0", - "git-raw-commits": "^2.0.8", - "git-remote-origin-url": "^2.0.0", - "git-semver-tags": "^4.1.1", - "lodash": "^4.17.15", - "normalize-package-data": "^3.0.0", - "q": "^1.5.1", - "read-pkg": "^3.0.0", - "read-pkg-up": "^3.0.0", - "through2": "^4.0.0" - } - }, - "conventional-changelog-ember": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-2.0.9.tgz", - "integrity": "sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==", - "requires": { - "q": "^1.5.1" - } - }, - "conventional-changelog-eslint": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-3.0.9.tgz", - "integrity": "sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==", - "requires": { - "q": "^1.5.1" - } - }, - "conventional-changelog-express": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-2.0.6.tgz", - "integrity": "sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==", - "requires": { - "q": "^1.5.1" - } - }, - "conventional-changelog-jquery": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-3.0.11.tgz", - "integrity": "sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==", - "requires": { - "q": "^1.5.1" - } - }, - "conventional-changelog-jshint": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.9.tgz", - "integrity": "sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==", - "requires": { - "compare-func": "^2.0.0", - "q": "^1.5.1" - } - }, - "conventional-changelog-preset-loader": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz", - "integrity": "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==" - }, - "conventional-changelog-writer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-5.0.1.tgz", - "integrity": "sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==", - "requires": { - "conventional-commits-filter": "^2.0.7", - "dateformat": "^3.0.0", - "handlebars": "^4.7.7", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "semver": "^6.0.0", - "split": "^1.0.0", - "through2": "^4.0.0" - } - }, - "conventional-commits-filter": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz", - "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==", - "requires": { - "lodash.ismatch": "^4.4.0", - "modify-values": "^1.0.0" - } - }, - "conventional-commits-parser": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz", - "integrity": "sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==", - "requires": { - "is-text-path": "^1.0.1", - "JSONStream": "^1.0.4", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - } - }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "dargs": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", - "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==" - }, - "dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" - }, - "decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", - "requires": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "dependencies": { - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" - } - } - }, - "dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "requires": { - "is-obj": "^2.0.0" - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "dev": true - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "requires": { - "locate-path": "^2.0.0" - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - }, - "get-pkg-repo": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz", - "integrity": "sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==", - "requires": { - "@hutson/parse-repository-url": "^3.0.0", - "hosted-git-info": "^4.0.0", - "through2": "^2.0.0", - "yargs": "^16.2.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } - } - }, - "git-raw-commits": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz", - "integrity": "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==", - "requires": { - "dargs": "^7.0.0", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - } - }, - "git-remote-origin-url": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", - "integrity": "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=", - "requires": { - "gitconfiglocal": "^1.0.0", - "pify": "^2.3.0" - } - }, - "git-semver-tags": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-4.1.1.tgz", - "integrity": "sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==", - "requires": { - "meow": "^8.0.0", - "semver": "^6.0.0" - } - }, - "gitconfiglocal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", - "integrity": "sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=", - "requires": { - "ini": "^1.3.2" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - } - }, - "hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==" - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "http2-client": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", - "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-core-module": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", - "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", - "requires": { - "has": "^1.0.3" - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" - }, - "is-text-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", - "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", - "requires": { - "text-extensions": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "requires": { - "argparse": "^2.0.1" - } - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" - }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - } - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" - }, - "lodash.ismatch": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", - "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=" - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==" - }, - "meow": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", - "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", - "requires": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "dependencies": { - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" - } - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==" - }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, - "minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - } - }, - "modify-values": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", - "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==" - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, - "node-fetch-h2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz", - "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", - "dev": true, - "requires": { - "http2-client": "^1.2.5" - } - }, - "normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "requires": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "oas-kit-common": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", - "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==", - "dev": true, - "requires": { - "fast-safe-stringify": "^2.0.7" - } - }, - "oas-linter": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz", - "integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==", - "dev": true, - "requires": { - "@exodus/schemasafe": "^1.0.0-rc.2", - "should": "^13.2.1", - "yaml": "^1.10.0" - } - }, - "oas-resolver": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz", - "integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==", - "dev": true, - "requires": { - "node-fetch-h2": "^2.3.0", - "oas-kit-common": "^1.0.8", - "reftools": "^1.1.9", - "yaml": "^1.10.0", - "yargs": "^17.0.1" - }, - "dependencies": { - "yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - } - }, - "yargs-parser": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", - "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", - "dev": true - } - } - }, - "oas-schema-walker": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz", - "integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==", - "dev": true - }, - "oas-validator": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz", - "integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==", - "dev": true, - "requires": { - "call-me-maybe": "^1.0.1", - "oas-kit-common": "^1.0.8", - "oas-linter": "^3.2.2", - "oas-resolver": "^2.5.6", - "oas-schema-walker": "^1.1.5", - "reftools": "^1.1.9", - "should": "^13.2.1", - "yaml": "^1.10.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - } - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "prettier": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", - "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" - }, - "quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==" - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - }, - "dependencies": { - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } - }, - "reftools": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz", - "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==", - "dev": true - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" - }, - "resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", - "requires": { - "is-core-module": "^2.8.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, - "should": { - "version": "13.2.3", - "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", - "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", - "dev": true, - "requires": { - "should-equal": "^2.0.0", - "should-format": "^3.0.3", - "should-type": "^1.4.0", - "should-type-adaptors": "^1.0.1", - "should-util": "^1.0.0" - } - }, - "should-equal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", - "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", - "dev": true, - "requires": { - "should-type": "^1.4.0" - } - }, - "should-format": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", - "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", - "dev": true, - "requires": { - "should-type": "^1.3.0", - "should-type-adaptors": "^1.0.1" - } - }, - "should-type": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", - "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==", - "dev": true - }, - "should-type-adaptors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", - "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", - "dev": true, - "requires": { - "should-type": "^1.3.0", - "should-util": "^1.0.0" - } - }, - "should-util": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", - "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", - "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==" - }, - "split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "requires": { - "through": "2" - } - }, - "split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", - "requires": { - "readable-stream": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" - }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "requires": { - "min-indent": "^1.0.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" - }, - "text-extensions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", - "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==" - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "through2": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", - "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", - "requires": { - "readable-stream": "3" - } - }, - "trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==" - }, - "type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==" - }, - "uglify-js": { - "version": "3.15.5", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.5.tgz", - "integrity": "sha512-hNM5q5GbBRB5xB+PMqVRcgYe4c8jbyZ1pzZhS6jbq54/4F2gFK869ZheiE5A8/t+W5jtTNpWef/5Q9zk639FNQ==", - "optional": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" - } - } -} diff --git a/package.json b/package.json index 1d79cda32..cec402ffd 100644 --- a/package.json +++ b/package.json @@ -1,27 +1,28 @@ { "name": "chii", - "version": "0.32.8", + "version": "0.34.0", "description": "tools to bundle openapi spec, not used in our server", "private": true, "scripts": { "format": "prettier -w --list-different ./", "test": "node openapi/test.js", + "build-common": "node scripts/build-common.mjs", "build": "node ./openapi/build.js" }, "prettier": { "printWidth": 120 }, "dependencies": { - "@apidevtools/json-schema-ref-parser": "^10.1.0", - "conventional-changelog": "^3.1.25", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21" + "@apidevtools/json-schema-ref-parser": "^15.3.5", + "js-yaml": "^4.1.1", + "lodash": "^4.18.1", + "yaml": "^2.9.0" }, "devDependencies": { - "@types/conventional-changelog": "^3.1.1", + "@types/json-schema": "^7.0.15", "colors": "^1.4.0", "oas-validator": "^5.0.8", - "prettier": "^2.8.7" + "prettier": "^3.8.3" }, "nodemonConfig": { "restartable": "rs", @@ -31,7 +32,8 @@ ], "legacyWatch": true, "delay": 2000, - "exec": "godotenv go run main.go --config config.yaml", + "exec": "task web", "ext": "go,json,html" - } + }, + "packageManager": "yarn@1.22.22" } diff --git a/pkg/duration/duration_test.go b/pkg/duration/duration_test.go index 3f87e5cd6..a05a2014e 100644 --- a/pkg/duration/duration_test.go +++ b/pkg/duration/duration_test.go @@ -64,7 +64,6 @@ func TestParse(t *testing.T) { } for _, tc := range testcases { - tc := tc t.Run(tc.Name, func(t *testing.T) { t.Parallel() actual, err := duration.Parse(tc.Input) diff --git a/pkg/readme.md b/pkg/readme.md deleted file mode 100644 index 561725056..000000000 --- a/pkg/readme.md +++ /dev/null @@ -1,7 +0,0 @@ -# public packages - -- `github.com/bangumi/server/pkg/wiki` wiki 解析 - -- `github.com/bangumi/server/pkg/duration` 解析时长 - -- `github.com/bangumi/server/pkg/vars` 一些预定义的常量,staff,relation 等 diff --git a/pkg/vars/common b/pkg/vars/common new file mode 160000 index 000000000..6a8442c17 --- /dev/null +++ b/pkg/vars/common @@ -0,0 +1 @@ +Subproject commit 6a8442c17143a870357a5ff812362e8b5cfe9f9d diff --git a/pkg/vars/enum/eptype.go b/pkg/vars/enum/eptype.go deleted file mode 100644 index 68b4341f3..000000000 --- a/pkg/vars/enum/eptype.go +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package enum - -// EpType -// Deprecated. -type EpType = int16 - -const ( - // EpTypeNormal - // Deprecated. - EpTypeNormal EpType = 0 - // EpTypeSpecial - // Deprecated. - EpTypeSpecial EpType = 1 - // EpTypeOpening - // Deprecated. - EpTypeOpening EpType = 2 - // EpTypeEnding - // Deprecated. - EpTypeEnding EpType = 3 - // EpTypeMad - // Deprecated. - EpTypeMad EpType = 4 - // EpTypeOther - // Deprecated. - EpTypeOther EpType = 6 -) diff --git a/pkg/vars/enum/user_group.go b/pkg/vars/enum/user_group.go deleted file mode 100644 index 252b19d2e..000000000 --- a/pkg/vars/enum/user_group.go +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package enum - -const ( - // UserGroupAdmin - // Deprecated. - UserGroupAdmin uint8 = iota + 1 - // UserGroupBangumiAdmin - // Deprecated. - UserGroupBangumiAdmin - // UserGroupWindowAdmin - // Deprecated. - UserGroupWindowAdmin - // UserGroupQuite - // Deprecated. - UserGroupQuite - // UserGroupBanned - // Deprecated. - UserGroupBanned - _ - _ - // UserGroupCharacterAdmin - // Deprecated. - UserGroupCharacterAdmin - // UserGroupWikiAdmin - // Deprecated. - UserGroupWikiAdmin - // UserGroupNormal - // Deprecated. - UserGroupNormal - // UserGroupWikiEditor - // Deprecated. - UserGroupWikiEditor -) diff --git a/pkg/vars/index.go b/pkg/vars/index.go index 6fb142cca..e0abb36ab 100644 --- a/pkg/vars/index.go +++ b/pkg/vars/index.go @@ -20,18 +20,19 @@ package vars import ( _ "embed" "encoding/json" + "fmt" "log" "github.com/bangumi/server/internal/model" ) -//go:embed staff.go.json +//go:embed staffs.go.json var staffRaw []byte //go:embed platform.go.json var platformRaw []byte -//go:embed relation.go.json +//go:embed relations.go.json var relationRaw []byte // StaffID ... @@ -54,20 +55,28 @@ var ( //nolint:gochecknoinits func init() { - if err := json.Unmarshal(staffRaw, &StaffMap); err != nil { + if err := json.Unmarshal(platformRaw, &PlatformMap); err != nil { log.Panicln("can't unmarshal raw staff json to go type", err) } - staffRaw = nil + platformRaw = nil - if err := json.Unmarshal(platformRaw, &PlatformMap); err != nil { - log.Panicln("can't unmarshal raw platform json to go type", err) + var staffsYaml struct { + Staffs map[model.SubjectType]map[StaffID]Staff `json:"staffs"` } - platformRaw = nil + if err := json.Unmarshal(staffRaw, &staffsYaml); err != nil { + log.Panicln("can't unmarshal raw staffs.go.json to go type", err) + } + staffRaw = nil + StaffMap = staffsYaml.Staffs - if err := json.Unmarshal(relationRaw, &RelationMap); err != nil { - log.Panicln("can't unmarshal raw relation json to go type", err) + var relationYAML struct { + Relations map[model.SubjectType]map[RelationID]Relation `json:"relations"` + } + if err := json.Unmarshal(relationRaw, &relationYAML); err != nil { + log.Panicln("can't unmarshal raw relations.go.json to go type", err) } relationRaw = nil + RelationMap = relationYAML.Relations } type Staff struct { @@ -99,7 +108,7 @@ type Relation struct { Description string `json:"description"` } -func (r Relation) String() string { +func (r Relation) String(id uint16) string { switch { case r.CN != "": return r.CN @@ -108,6 +117,6 @@ func (r Relation) String() string { case r.EN != "": return r.EN default: - return "unknown" + return fmt.Sprintf("unknown(%d)", id) } } diff --git a/pkg/vars/platform.go.json b/pkg/vars/platform.go.json index 07a299a63..82d273bcc 100644 --- a/pkg/vars/platform.go.json +++ b/pkg/vars/platform.go.json @@ -1,374 +1,250 @@ { "1": { "0": { - "alias": "misc", "id": 0, "type": "other", "type_cn": "其他", - "wiki_tpl": "Book" + "alias": "misc", + "wiki_tpl": "Book", + "order": 6 }, "1001": { - "alias": "comic", - "enable_header": true, "id": 1001, "type": "Comic", "type_cn": "漫画", - "wiki_tpl": "Manga" + "alias": "comic", + "wiki_tpl": "Manga", + "enable_header": true, + "order": 0 }, "1002": { - "alias": "novel", - "enable_header": true, "id": 1002, "type": "Novel", "type_cn": "小说", - "wiki_tpl": "Novel" + "alias": "novel", + "wiki_tpl": "Novel", + "enable_header": true, + "order": 1 }, "1003": { - "alias": "illustration", - "enable_header": true, "id": 1003, "type": "Illustration", "type_cn": "画集", - "wiki_tpl": "Book" + "alias": "illustration", + "wiki_tpl": "Book", + "enable_header": true, + "order": 2 + }, + "1004": { + "id": 1004, + "type": "Picture", + "type_cn": "绘本", + "alias": "picture", + "wiki_tpl": "Book", + "enable_header": true, + "order": 3 + }, + "1005": { + "id": 1005, + "type": "Photo", + "type_cn": "写真", + "alias": "photo", + "wiki_tpl": "PhotoBook", + "enable_header": true, + "order": 5 + }, + "1006": { + "id": 1006, + "type": "Official", + "type_cn": "公式书", + "alias": "official", + "wiki_tpl": "Book", + "enable_header": true, + "order": 4 } }, "2": { "0": { - "alias": "misc", "id": 0, "type": "other", "type_cn": "其他", - "wiki_tpl": "Anime" + "alias": "misc", + "wiki_tpl": "Anime", + "order": 5, + "sort_keys": ["放送开始", "发售日", "发售日期", "上映年度", "上映日"] }, "1": { - "alias": "tv", - "enable_header": true, "id": 1, "type": "TV", "type_cn": "TV", - "wiki_tpl": "TVAnime" + "alias": "tv", + "enable_header": true, + "wiki_tpl": "TVAnime", + "order": 0, + "sort_keys": ["放送开始", "发售日", "发售日期", "上映年度", "上映日"] }, "2": { - "alias": "ova", - "enable_header": true, "id": 2, "type": "OVA", "type_cn": "OVA", - "wiki_tpl": "OVA" + "alias": "ova", + "enable_header": true, + "wiki_tpl": "OVA", + "order": 2, + "sort_keys": ["放送开始", "发售日", "发售日期", "上映年度", "上映日"] }, "3": { - "alias": "movie", - "enable_header": true, "id": 3, "type": "movie", "type_cn": "剧场版", - "wiki_tpl": "Movie" + "alias": "movie", + "enable_header": true, + "wiki_tpl": "Movie", + "order": 3, + "sort_keys": ["放送开始", "发售日", "发售日期", "上映年度", "上映日"] }, "5": { - "alias": "web", - "enable_header": true, "id": 5, "type": "web", "type_cn": "WEB", - "wiki_tpl": "TVAnime" + "alias": "web", + "enable_header": true, + "wiki_tpl": "TVAnime", + "order": 1, + "sort_keys": ["放送开始", "发售日", "发售日期", "上映年度", "上映日"] + }, + "2006": { + "id": 2006, + "type": "anime_comic", + "type_cn": "动态漫画", + "alias": "anime_comic", + "enable_header": true, + "wiki_tpl": "TVAnime", + "order": 4 } }, "3": { "0": { "id": 0, "type": "", - "type_cn": "" - }, - "1": { - "id": 1, - "type": "TV", - "type_cn": "TV版动画" - }, - "2": { - "id": 2, - "url": "ova", - "type": "OVA", - "type_cn": "OVA" - }, - "3": { - "id": 3, - "url": "movie", - "type": "Movie", - "type_cn": "剧场版动画" + "type_cn": "", + "alias": "", + "wiki_tpl": "", + "order": 0 } }, "4": { "0": { "id": 0, - "type": "", - "type_cn": "全部游戏" - }, - "4": { - "alias": "pc", - "id": 4, - "search_string": "pc|windows", - "type": "PC", - "type_cn": "PC" - }, - "5": { - "alias": "nds", - "id": 5, - "search_string": "nds", - "type": "NDS", - "type_cn": "NDS" - }, - "6": { - "alias": "psp", - "id": 6, - "search_string": "psp", - "type": "PSP", - "type_cn": "PSP" - }, - "7": { - "alias": "ps2", - "id": 7, - "search_string": "PS2", - "type": "PS2", - "type_cn": "PS2" - }, - "8": { - "alias": "ps3", - "id": 8, - "search_string": "PS3|(PlayStation 3)", - "type": "PS3", - "type_cn": "PS3" - }, - "9": { - "alias": "xbox360", - "id": 9, - "search_string": "xbox360", - "type": "Xbox360", - "type_cn": "Xbox360" - }, - "10": { - "alias": "wii", - "id": 10, - "search_string": "Wii", - "type": "Wii", - "type_cn": "Wii" - }, - "11": { - "alias": "iphone", - "id": 11, - "search_string": "iphone|ipad|ios", - "type": "iOS", - "type_cn": "iOS" - }, - "12": { - "alias": "arc", - "id": 12, - "search_string": "ARC|街机", - "type": "ARC", - "type_cn": "街机" - }, - "15": { - "alias": "xbox", - "id": 15, - "search_string": "XBOX", - "type": "XBOX", - "type_cn": "XBOX" - }, - "16": { - "id": 16, - "type": "GameCube", - "type_cn": "GameCube" - }, - "17": { - "alias": "gamecube", - "id": 17, - "search_string": "GameCube|ngc", - "type": "GameCube", - "type_cn": "GameCube" - }, - "18": { - "alias": "ngp", - "id": 18, - "search_string": "ngp", - "type": "NEOGEO Pocket Color", - "type_cn": "NEOGEO Pocket Color" - }, - "19": { - "alias": "sfc", - "id": 19, - "search_string": "SFC", - "type": "SFC", - "type_cn": "SFC" - }, - "20": { - "alias": "fc", - "id": 20, - "search_string": "FC", - "type": "FC", - "type_cn": "FC" - }, - "21": { - "alias": "n64", - "id": 21, - "search_string": "n64", - "type": "Nintendo 64", - "type_cn": "Nintendo 64" - }, - "22": { - "alias": "GBA", - "id": 22, - "search_string": "GBA", - "type": "GBA", - "type_cn": "GBA" - }, - "23": { - "alias": "GB", - "id": 23, - "search_string": "GB", - "type": "GB", - "type_cn": "GB" - }, - "24": { - "id": 24, - "type": "GBC", - "type_cn": "GBC" - }, - "25": { - "alias": "vb", - "id": 25, - "search_string": "Virtual Boy", - "type": "Virtual Boy", - "type_cn": "Virtual Boy" - }, - "26": { - "alias": "wsc", - "id": 26, - "search_string": "wsc", - "type": "WonderSwan Color", - "type_cn": "WonderSwan Color" - }, - "27": { - "alias": "dreamcast", - "id": 27, - "search_string": "dc", - "type": "Dreamcast", - "type_cn": "Dreamcast" - }, - "28": { - "alias": "ps", - "id": 28, - "search_string": "ps", - "type": "PlayStation", - "type_cn": "PlayStation" - }, - "29": { - "alias": "ws", - "id": 29, - "search_string": "ws", - "type": "WonderSwan", - "type_cn": "WonderSwan" - }, - "30": { - "alias": "psv", - "id": 30, - "search_string": "psv|vita", - "type": "PSVita", - "type_cn": "PS Vita" - }, - "31": { - "alias": "3ds", - "id": 31, - "search_string": "3ds", - "type": "3DS", - "type_cn": "3DS" - }, - "32": { - "alias": "android", - "id": 32, - "search_string": "android", - "type": "Android", - "type_cn": "Android" - }, - "33": { - "alias": "mac", - "id": 33, - "search_string": "mac", - "type": "Mac OS", - "type_cn": "Mac OS" - }, - "34": { - "alias": "ps4", - "id": 34, - "search_string": "PS4", - "type": "PS4", - "type_cn": "PS4" - }, - "35": { - "alias": "xbox_one", - "id": 35, - "search_string": "(Xbox One)", - "type": "Xbox One", - "type_cn": "Xbox One" + "type": "other", + "type_cn": "其他", + "alias": "misc", + "order": 4 }, - "36": { - "alias": "wii_u", - "id": 36, - "search_string": "(Wii U)|WiiU", - "type": "Wii U", - "type_cn": "Wii U" + "4001": { + "id": 4001, + "type": "games", + "type_cn": "游戏", + "alias": "games", + "enable_header": true, + "order": 0 }, - "37": { - "alias": "ns", - "id": 37, - "search_string": "(Nintendo Switch)|NS", - "type": "Nintendo Switch", - "type_cn": "Nintendo Switch" + "4002": { + "id": 4002, + "type": "software", + "type_cn": "软件", + "alias": "software", + "enable_header": true, + "order": 2 }, - "38": { - "alias": "ps5", - "id": 38, - "search_string": "PS5", - "type": "PS5", - "type_cn": "PS5" + "4003": { + "id": 4003, + "type": "dlc", + "type_cn": "扩展包", + "alias": "dlc", + "enable_header": true, + "order": 1 }, - "39": { - "alias": "xbox_series_xs", - "id": 39, - "search_string": "XSX|XSS|(Xbox Series X)|(Xbox Series S)", - "type": "Xbox Series X/S", - "type_cn": "Xbox Series X/S" + "4005": { + "id": 4005, + "type": "tabletop", + "type_cn": "桌游", + "alias": "tabletop", + "enable_header": true, + "order": 3 } }, "6": { "0": { - "alias": "misc", "id": 0, "type": "other", "type_cn": "其他", - "wiki_tpl": "TV" + "alias": "misc", + "wiki_tpl": "TV", + "order": 7 }, "1": { - "alias": "jp", - "enable_header": true, "id": 1, "type": "jp", "type_cn": "日剧", - "wiki_tpl": "TV" + "alias": "jp", + "enable_header": true, + "wiki_tpl": "TV", + "order": 0 }, "2": { - "alias": "en", - "enable_header": true, "id": 2, "type": "en", "type_cn": "欧美剧", - "wiki_tpl": "TV" + "alias": "en", + "enable_header": true, + "wiki_tpl": "TV", + "order": 1 }, "3": { - "alias": "cn", - "enable_header": true, "id": 3, "type": "cn", "type_cn": "华语剧", - "wiki_tpl": "TV" + "alias": "cn", + "enable_header": true, + "wiki_tpl": "TV", + "order": 2 + }, + "6001": { + "id": 6001, + "type": "tv", + "type_cn": "电视剧", + "alias": "tv", + "enable_header": true, + "wiki_tpl": "TV", + "order": 3 + }, + "6002": { + "id": 6002, + "type": "movie", + "type_cn": "电影", + "alias": "movie", + "enable_header": true, + "wiki_tpl": "realMovie", + "order": 4 + }, + "6003": { + "id": 6003, + "type": "live", + "type_cn": "演出", + "alias": "live", + "enable_header": true, + "wiki_tpl": "TV", + "order": 5 + }, + "6004": { + "id": 6004, + "type": "show", + "type_cn": "综艺", + "alias": "show", + "enable_header": true, + "wiki_tpl": "TV", + "order": 6 } } } diff --git a/pkg/vars/relation.go.json b/pkg/vars/relation.go.json deleted file mode 100644 index b7904897f..000000000 --- a/pkg/vars/relation.go.json +++ /dev/null @@ -1,354 +0,0 @@ -{ - "1": { - "1": { - "cn": "改编", - "description": "同系列不同平台作品,如柯南漫画与动画版", - "en": "Adaptation", - "jp": "" - }, - "1002": { - "cn": "系列", - "description": "", - "en": "Series", - "jp": "" - }, - "1003": { - "cn": "单行本", - "description": "", - "en": "Offprint", - "jp": "" - }, - "1004": { - "cn": "画集", - "description": "", - "en": "Album", - "jp": "" - }, - "1005": { - "cn": "前传", - "description": "发生在故事之前", - "en": "Prequel", - "jp": "" - }, - "1006": { - "cn": "续集", - "description": "发生在故事之后", - "en": "Sequel", - "jp": "" - }, - "1007": { - "cn": "番外篇", - "description": "", - "en": "Side Story", - "jp": "" - }, - "1008": { - "cn": "主线故事", - "description": "", - "en": "Parent Story", - "jp": "" - }, - "1010": { - "cn": "不同版本", - "description": "", - "en": "Alternative version", - "jp": "" - }, - "1011": { - "cn": "角色出演", - "description": "相同角色,没有关联的故事", - "en": "Character", - "jp": "" - }, - "1012": { - "cn": "相同世界观", - "description": "发生在同一个世界观/时间线下,不同的出演角色", - "en": "Same setting", - "jp": "" - }, - "1013": { - "cn": "不同世界观", - "description": "相同的出演角色,不同的世界观/时间线设定", - "en": "Alternative setting", - "jp": "" - }, - "1099": { - "cn": "其他", - "description": "", - "en": "Other", - "jp": "" - } - }, - "2": { - "1": { - "cn": "改编", - "description": "同系列不同平台作品,如柯南漫画与动画版", - "en": "Adaptation", - "jp": "" - }, - "2": { - "cn": "前传", - "description": "发生在故事之前", - "en": "Prequel", - "jp": "" - }, - "3": { - "cn": "续集", - "description": "发生在故事之后", - "en": "Sequel", - "jp": "" - }, - "4": { - "cn": "总集篇", - "description": "对故事的概括版本", - "en": "Summary", - "jp": "" - }, - "5": { - "cn": "全集", - "description": "相对于剧场版/总集篇的完整故事", - "en": "Full Story", - "jp": "" - }, - "6": { - "cn": "番外篇", - "description": "", - "en": "Side Story", - "jp": "" - }, - "7": { - "cn": "角色出演", - "description": "相同角色,没有关联的故事", - "en": "Character", - "jp": "" - }, - "8": { - "cn": "相同世界观", - "description": "发生在同一个世界观/时间线下,不同的出演角色", - "en": "Same setting", - "jp": "" - }, - "9": { - "cn": "不同世界观", - "description": "相同的出演角色,不同的世界观/时间线设定", - "en": "Alternative setting", - "jp": "" - }, - "10": { - "cn": "不同演绎", - "description": "相同设定、角色,不同的演绎方式(如EVA原作与新剧场版)", - "en": "Alternative version", - "jp": "" - }, - "11": { - "cn": "衍生", - "description": "如柯南与魔术快斗", - "en": "Spin-off", - "jp": "" - }, - "12": { - "cn": "主线故事", - "description": "", - "en": "Parent Story", - "jp": "" - }, - "99": { - "cn": "其他", - "description": "", - "en": "Other", - "jp": "" - } - }, - "3": { - "3001": { - "cn": "原声集", - "description": "", - "en": "OST", - "jp": "" - }, - "3002": { - "cn": "角色歌", - "description": "", - "en": "Character Song", - "jp": "" - }, - "3003": { - "cn": "片头曲", - "description": "", - "en": "Opening Song", - "jp": "" - }, - "3004": { - "cn": "片尾曲", - "description": "", - "en": "Ending Song", - "jp": "" - }, - "3005": { - "cn": "插入歌", - "description": "", - "en": "Insert Song", - "jp": "" - }, - "3006": { - "cn": "印象曲", - "description": "", - "en": "Image Song", - "jp": "" - }, - "3007": { - "cn": "广播剧", - "description": "", - "en": "Drama", - "jp": "" - }, - "3099": { - "cn": "其他", - "description": "", - "en": "Other", - "jp": "" - } - }, - "4": { - "1": { - "cn": "改编", - "description": "同系列不同平台作品,如 CLANNAD 游戏与动画版", - "en": "Adaptation", - "jp": "" - }, - "4002": { - "cn": "前传", - "description": "发生在故事之前", - "en": "Prequel", - "jp": "" - }, - "4003": { - "cn": "续集", - "description": "发生在故事之后", - "en": "Sequel", - "jp": "" - }, - "4006": { - "cn": "资料片、外传", - "description": "", - "en": "Side Story", - "jp": "" - }, - "4007": { - "cn": "角色出演", - "description": "相同角色,没有关联的故事", - "en": "Character", - "jp": "" - }, - "4008": { - "cn": "相同世界观", - "description": "发生在同一个世界观/时间线下,不同的出演角色", - "en": "Same setting", - "jp": "" - }, - "4009": { - "cn": "不同世界观", - "description": "相同的出演角色,不同的世界观/时间线设定", - "en": "Alternative setting", - "jp": "" - }, - "4010": { - "cn": "不同演绎", - "description": "相同设定、角色,不同的演绎方式", - "en": "Alternative version", - "jp": "" - }, - "4012": { - "cn": "主线故事", - "description": "", - "en": "Parent Story", - "jp": "" - }, - "4099": { - "cn": "其他", - "description": "", - "en": "Other", - "jp": "" - } - }, - "6": { - "1": { - "cn": "改编", - "description": "同系列不同平台作品,如柯南漫画与动画版", - "en": "Adaptation", - "jp": "" - }, - "2": { - "cn": "前传", - "description": "发生在故事之前", - "en": "Prequel", - "jp": "" - }, - "3": { - "cn": "续集", - "description": "发生在故事之后", - "en": "Sequel", - "jp": "" - }, - "4": { - "cn": "总集篇", - "description": "对故事的概括版本", - "en": "Summary", - "jp": "" - }, - "5": { - "cn": "全集", - "description": "相对于剧场版/总集篇的完整故事", - "en": "Full Story", - "jp": "" - }, - "6": { - "cn": "番外篇", - "description": "", - "en": "Side Story", - "jp": "" - }, - "7": { - "cn": "角色出演", - "description": "相同角色,没有关联的故事", - "en": "Character", - "jp": "" - }, - "8": { - "cn": "相同世界观", - "description": "发生在同一个世界观/时间线下,不同的出演角色", - "en": "Same setting", - "jp": "" - }, - "9": { - "cn": "不同世界观", - "description": "相同的出演角色,不同的世界观/时间线设定", - "en": "Alternative setting", - "jp": "" - }, - "10": { - "cn": "不同演绎", - "description": "相同设定、角色,不同的演绎方式(如EVA原作与新剧场版)", - "en": "Alternative version", - "jp": "" - }, - "11": { - "cn": "衍生", - "description": "如柯南与魔术快斗", - "en": "Spin-off", - "jp": "" - }, - "12": { - "cn": "主线故事", - "description": "", - "en": "Parent Story", - "jp": "" - }, - "99": { - "cn": "其他", - "description": "", - "en": "Other", - "jp": "" - } - } -} diff --git a/pkg/vars/relations.go.json b/pkg/vars/relations.go.json new file mode 100644 index 000000000..f17d469ad --- /dev/null +++ b/pkg/vars/relations.go.json @@ -0,0 +1,769 @@ +{ + "define": { + "type": { + "book": 1, + "anime": 2, + "music": 3, + "game": 4, + "real": 6 + }, + "types": { + "anime": { + "1": { + "en": "Adaptation", + "cn": "改编", + "jp": "", + "desc": "同系列不同平台作品(如柯南漫画与动画版)" + }, + "2": { + "en": "Prequel", + "cn": "前传", + "jp": "", + "desc": "发生在故事之前" + }, + "3": { + "en": "Sequel", + "cn": "续集", + "jp": "", + "desc": "发生在故事之后" + }, + "4": { + "en": "Summary", + "cn": "总集篇", + "jp": "", + "desc": "对故事的概括版本" + }, + "5": { + "en": "Full Story", + "cn": "全集", + "jp": "", + "desc": "相对于剧场版/总集篇的完整故事" + }, + "6": { + "en": "Side Story", + "cn": "番外篇", + "jp": "", + "desc": "" + }, + "7": { + "en": "Character", + "cn": "角色出演", + "jp": "", + "desc": "相同角色,没有关联的故事" + }, + "8": { + "en": "Same Setting", + "cn": "相同世界观", + "jp": "", + "desc": "发生在同一个世界观/时间线下,不同的出演角色" + }, + "9": { + "en": "Alternative Setting", + "cn": "不同世界观", + "jp": "", + "desc": "相同的主演角色,不同的世界观/时间线设定" + }, + "10": { + "en": "Alternative Version", + "cn": "不同演绎", + "jp": "", + "desc": "相同设定、角色,不同的演绎方式(如EVA原作与新剧场版)" + }, + "11": { + "en": "Spin-off", + "cn": "衍生", + "jp": "", + "desc": "世界观相同,角色主线与有关联或来自主线,但又非主线的主角们" + }, + "12": { + "en": "Parent Story", + "cn": "主线故事", + "jp": "", + "desc": "" + }, + "14": { + "en": "Collaboration", + "cn": "联动", + "jp": "", + "desc": "出现了被关联作品中的角色", + "skip_vice_versa": true + }, + "99": { + "en": "Other", + "cn": "其他", + "jp": "", + "desc": "", + "skip_vice_versa": true + } + }, + "book": { + "1": { + "en": "Adaptation", + "cn": "改编", + "jp": "", + "desc": "同系列不同平台作品(如柯南漫画与动画版)" + }, + "1002": { + "en": "Series", + "cn": "系列", + "jp": "", + "desc": "" + }, + "1003": { + "en": "Offprint", + "cn": "单行本", + "jp": "", + "desc": "" + }, + "1004": { + "en": "Album", + "cn": "画集", + "jp": "", + "desc": "" + }, + "1005": { + "en": "Prequel", + "cn": "前传", + "jp": "", + "desc": "发生在故事之前" + }, + "1006": { + "en": "Sequel", + "cn": "续集", + "jp": "", + "desc": "发生在故事之后" + }, + "1007": { + "en": "Side Story", + "cn": "番外篇", + "jp": "", + "desc": "" + }, + "1008": { + "en": "Parent Story", + "cn": "主线故事", + "jp": "", + "desc": "" + }, + "1010": { + "en": "Version", + "cn": "不同版本", + "jp": "", + "desc": "" + }, + "1011": { + "en": "Character", + "cn": "角色出演", + "jp": "", + "desc": "相同角色,没有关联的故事" + }, + "1012": { + "en": "Same setting", + "cn": "相同世界观", + "jp": "", + "desc": "发生在同一个世界观/时间线下,不同的出演角色" + }, + "1013": { + "en": "Alternative setting", + "cn": "不同世界观", + "jp": "", + "desc": "相同的出演角色,不同的世界观/时间线设定" + }, + "1014": { + "en": "Collaboration", + "cn": "联动", + "jp": "", + "desc": "出现了被关联作品中的角色", + "skip_vice_versa": true + }, + "1015": { + "en": "Alternative Version", + "cn": "不同演绎", + "jp": "", + "desc": "相同设定、角色,不同的演绎方式" + }, + "1099": { + "en": "Other", + "cn": "其他", + "jp": "", + "desc": "", + "skip_vice_versa": true + } + }, + "music": { + "3001": { + "en": "OST", + "cn": "原声集", + "jp": "", + "desc": "" + }, + "3002": { + "en": "Character Song", + "cn": "角色歌", + "jp": "", + "desc": "" + }, + "3003": { + "en": "Opening Song", + "cn": "片头曲", + "jp": "", + "desc": "" + }, + "3004": { + "en": "Ending Song", + "cn": "片尾曲", + "jp": "", + "desc": "" + }, + "3005": { + "en": "Insert Song", + "cn": "插入歌", + "jp": "", + "desc": "" + }, + "3006": { + "en": "Image Song", + "cn": "印象曲", + "jp": "", + "desc": "" + }, + "3007": { + "en": "Drama", + "cn": "广播剧", + "jp": "", + "desc": "" + }, + "3099": { + "en": "Other", + "cn": "其他", + "jp": "", + "desc": "", + "skip_vice_versa": true + } + }, + "game": { + "1": { + "en": "Adaptation", + "cn": "改编", + "jp": "", + "desc": "同系列不同平台作品(如 CLANNAD 游戏与动画版)" + }, + "4002": { + "en": "Prequel", + "cn": "前传", + "jp": "", + "desc": "发生在故事之前/或作品发售之前" + }, + "4003": { + "en": "Sequel", + "cn": "续集", + "jp": "", + "desc": "发生在故事之后/或作品发售之后" + }, + "4006": { + "en": "Side Story", + "cn": "外传", + "jp": "", + "desc": "" + }, + "4007": { + "en": "Character", + "cn": "角色出演", + "jp": "", + "desc": "相同角色,没有关联的故事" + }, + "4008": { + "en": "Same Setting", + "cn": "相同世界观", + "jp": "", + "desc": "发生在同一个世界观/时间线下,不同的出演角色" + }, + "4009": { + "en": "Alternative Setting", + "cn": "不同世界观", + "jp": "", + "desc": "相同的出演角色,不同的世界观/时间线设定" + }, + "4010": { + "en": "Alternative Version", + "cn": "不同演绎", + "jp": "", + "desc": "相同设定、角色,不同的演绎方式" + }, + "4012": { + "en": "Parent Story", + "cn": "主线故事", + "jp": "", + "desc": "" + }, + "4014": { + "en": "Collaboration", + "cn": "联动", + "jp": "", + "desc": "出现了被关联作品中的角色", + "skip_vice_versa": true + }, + "4015": { + "en": "DLC", + "cn": "扩展包", + "jp": "", + "desc": "" + }, + "4016": { + "en": "Version", + "cn": "不同版本", + "jp": "", + "desc": "相同故事、角色,画面、音乐或系统改进" + }, + "4017": { + "en": "Main Version", + "cn": "主版本", + "jp": "", + "desc": "游戏最初发售时的版本" + }, + "4018": { + "en": "Collection", + "cn": "合集", + "jp": "", + "desc": "收录本作品的合集条目" + }, + "4019": { + "en": "In Collection", + "cn": "收录作品", + "jp": "", + "desc": "合集条目中收录的作品" + }, + "4099": { + "en": "Other", + "cn": "其他", + "jp": "", + "desc": "", + "skip_vice_versa": true + } + } + } + }, + "relations": { + "1": { + "1": { + "en": "Adaptation", + "cn": "改编", + "jp": "", + "desc": "同系列不同平台作品(如柯南漫画与动画版)" + }, + "1002": { + "en": "Series", + "cn": "系列", + "jp": "", + "desc": "" + }, + "1003": { + "en": "Offprint", + "cn": "单行本", + "jp": "", + "desc": "" + }, + "1004": { + "en": "Album", + "cn": "画集", + "jp": "", + "desc": "" + }, + "1005": { + "en": "Prequel", + "cn": "前传", + "jp": "", + "desc": "发生在故事之前" + }, + "1006": { + "en": "Sequel", + "cn": "续集", + "jp": "", + "desc": "发生在故事之后" + }, + "1007": { + "en": "Side Story", + "cn": "番外篇", + "jp": "", + "desc": "" + }, + "1008": { + "en": "Parent Story", + "cn": "主线故事", + "jp": "", + "desc": "" + }, + "1010": { + "en": "Version", + "cn": "不同版本", + "jp": "", + "desc": "" + }, + "1011": { + "en": "Character", + "cn": "角色出演", + "jp": "", + "desc": "相同角色,没有关联的故事" + }, + "1012": { + "en": "Same setting", + "cn": "相同世界观", + "jp": "", + "desc": "发生在同一个世界观/时间线下,不同的出演角色" + }, + "1013": { + "en": "Alternative setting", + "cn": "不同世界观", + "jp": "", + "desc": "相同的出演角色,不同的世界观/时间线设定" + }, + "1014": { + "en": "Collaboration", + "cn": "联动", + "jp": "", + "desc": "出现了被关联作品中的角色", + "skip_vice_versa": true + }, + "1015": { + "en": "Alternative Version", + "cn": "不同演绎", + "jp": "", + "desc": "相同设定、角色,不同的演绎方式" + }, + "1099": { + "en": "Other", + "cn": "其他", + "jp": "", + "desc": "", + "skip_vice_versa": true + } + }, + "2": { + "1": { + "en": "Adaptation", + "cn": "改编", + "jp": "", + "desc": "同系列不同平台作品(如柯南漫画与动画版)" + }, + "2": { + "en": "Prequel", + "cn": "前传", + "jp": "", + "desc": "发生在故事之前" + }, + "3": { + "en": "Sequel", + "cn": "续集", + "jp": "", + "desc": "发生在故事之后" + }, + "4": { + "en": "Summary", + "cn": "总集篇", + "jp": "", + "desc": "对故事的概括版本" + }, + "5": { + "en": "Full Story", + "cn": "全集", + "jp": "", + "desc": "相对于剧场版/总集篇的完整故事" + }, + "6": { + "en": "Side Story", + "cn": "番外篇", + "jp": "", + "desc": "" + }, + "7": { + "en": "Character", + "cn": "角色出演", + "jp": "", + "desc": "相同角色,没有关联的故事" + }, + "8": { + "en": "Same Setting", + "cn": "相同世界观", + "jp": "", + "desc": "发生在同一个世界观/时间线下,不同的出演角色" + }, + "9": { + "en": "Alternative Setting", + "cn": "不同世界观", + "jp": "", + "desc": "相同的主演角色,不同的世界观/时间线设定" + }, + "10": { + "en": "Alternative Version", + "cn": "不同演绎", + "jp": "", + "desc": "相同设定、角色,不同的演绎方式(如EVA原作与新剧场版)" + }, + "11": { + "en": "Spin-off", + "cn": "衍生", + "jp": "", + "desc": "世界观相同,角色主线与有关联或来自主线,但又非主线的主角们" + }, + "12": { + "en": "Parent Story", + "cn": "主线故事", + "jp": "", + "desc": "" + }, + "14": { + "en": "Collaboration", + "cn": "联动", + "jp": "", + "desc": "出现了被关联作品中的角色", + "skip_vice_versa": true + }, + "99": { + "en": "Other", + "cn": "其他", + "jp": "", + "desc": "", + "skip_vice_versa": true + } + }, + "3": { + "3001": { + "en": "OST", + "cn": "原声集", + "jp": "", + "desc": "" + }, + "3002": { + "en": "Character Song", + "cn": "角色歌", + "jp": "", + "desc": "" + }, + "3003": { + "en": "Opening Song", + "cn": "片头曲", + "jp": "", + "desc": "" + }, + "3004": { + "en": "Ending Song", + "cn": "片尾曲", + "jp": "", + "desc": "" + }, + "3005": { + "en": "Insert Song", + "cn": "插入歌", + "jp": "", + "desc": "" + }, + "3006": { + "en": "Image Song", + "cn": "印象曲", + "jp": "", + "desc": "" + }, + "3007": { + "en": "Drama", + "cn": "广播剧", + "jp": "", + "desc": "" + }, + "3099": { + "en": "Other", + "cn": "其他", + "jp": "", + "desc": "", + "skip_vice_versa": true + } + }, + "4": { + "1": { + "en": "Adaptation", + "cn": "改编", + "jp": "", + "desc": "同系列不同平台作品(如 CLANNAD 游戏与动画版)" + }, + "4002": { + "en": "Prequel", + "cn": "前传", + "jp": "", + "desc": "发生在故事之前/或作品发售之前" + }, + "4003": { + "en": "Sequel", + "cn": "续集", + "jp": "", + "desc": "发生在故事之后/或作品发售之后" + }, + "4006": { + "en": "Side Story", + "cn": "外传", + "jp": "", + "desc": "" + }, + "4007": { + "en": "Character", + "cn": "角色出演", + "jp": "", + "desc": "相同角色,没有关联的故事" + }, + "4008": { + "en": "Same Setting", + "cn": "相同世界观", + "jp": "", + "desc": "发生在同一个世界观/时间线下,不同的出演角色" + }, + "4009": { + "en": "Alternative Setting", + "cn": "不同世界观", + "jp": "", + "desc": "相同的出演角色,不同的世界观/时间线设定" + }, + "4010": { + "en": "Alternative Version", + "cn": "不同演绎", + "jp": "", + "desc": "相同设定、角色,不同的演绎方式" + }, + "4012": { + "en": "Parent Story", + "cn": "主线故事", + "jp": "", + "desc": "" + }, + "4014": { + "en": "Collaboration", + "cn": "联动", + "jp": "", + "desc": "出现了被关联作品中的角色", + "skip_vice_versa": true + }, + "4015": { + "en": "DLC", + "cn": "扩展包", + "jp": "", + "desc": "" + }, + "4016": { + "en": "Version", + "cn": "不同版本", + "jp": "", + "desc": "相同故事、角色,画面、音乐或系统改进" + }, + "4017": { + "en": "Main Version", + "cn": "主版本", + "jp": "", + "desc": "游戏最初发售时的版本" + }, + "4018": { + "en": "Collection", + "cn": "合集", + "jp": "", + "desc": "收录本作品的合集条目" + }, + "4019": { + "en": "In Collection", + "cn": "收录作品", + "jp": "", + "desc": "合集条目中收录的作品" + }, + "4099": { + "en": "Other", + "cn": "其他", + "jp": "", + "desc": "", + "skip_vice_versa": true + } + }, + "6": { + "1": { + "en": "Adaptation", + "cn": "改编", + "jp": "", + "desc": "同系列不同平台作品(如柯南漫画与动画版)" + }, + "2": { + "en": "Prequel", + "cn": "前传", + "jp": "", + "desc": "发生在故事之前" + }, + "3": { + "en": "Sequel", + "cn": "续集", + "jp": "", + "desc": "发生在故事之后" + }, + "4": { + "en": "Summary", + "cn": "总集篇", + "jp": "", + "desc": "对故事的概括版本" + }, + "5": { + "en": "Full Story", + "cn": "全集", + "jp": "", + "desc": "相对于剧场版/总集篇的完整故事" + }, + "6": { + "en": "Side Story", + "cn": "番外篇", + "jp": "", + "desc": "" + }, + "7": { + "en": "Character", + "cn": "角色出演", + "jp": "", + "desc": "相同角色,没有关联的故事" + }, + "8": { + "en": "Same Setting", + "cn": "相同世界观", + "jp": "", + "desc": "发生在同一个世界观/时间线下,不同的出演角色" + }, + "9": { + "en": "Alternative Setting", + "cn": "不同世界观", + "jp": "", + "desc": "相同的主演角色,不同的世界观/时间线设定" + }, + "10": { + "en": "Alternative Version", + "cn": "不同演绎", + "jp": "", + "desc": "相同设定、角色,不同的演绎方式(如EVA原作与新剧场版)" + }, + "11": { + "en": "Spin-off", + "cn": "衍生", + "jp": "", + "desc": "世界观相同,角色主线与有关联或来自主线,但又非主线的主角们" + }, + "12": { + "en": "Parent Story", + "cn": "主线故事", + "jp": "", + "desc": "" + }, + "14": { + "en": "Collaboration", + "cn": "联动", + "jp": "", + "desc": "出现了被关联作品中的角色", + "skip_vice_versa": true + }, + "99": { + "en": "Other", + "cn": "其他", + "jp": "", + "desc": "", + "skip_vice_versa": true + } + } + } +} diff --git a/pkg/vars/staff.go.json b/pkg/vars/staff.go.json deleted file mode 100644 index 08ee62874..000000000 --- a/pkg/vars/staff.go.json +++ /dev/null @@ -1,876 +0,0 @@ -{ - "1": { - "2001": { - "CN": "作者", - "EN": "", - "JP": "", - "RDF": "" - }, - "2002": { - "CN": "", - "EN": "", - "JP": "作画", - "RDF": "" - }, - "2003": { - "CN": "插图", - "EN": "", - "JP": "イラスト", - "RDF": "" - }, - "2004": { - "CN": "", - "EN": "", - "JP": "出版社", - "RDF": "" - }, - "2005": { - "CN": "连载杂志", - "EN": "", - "JP": "掲載誌", - "RDF": "" - }, - "2006": { - "CN": "译者", - "EN": "", - "JP": "", - "RDF": "" - }, - "2007": { - "CN": "原作", - "EN": "Original Creator/Original Work", - "JP": "", - "RDF": "" - }, - "2008": { - "CN": "客串", - "EN": "guest", - "JP": "ゲスト", - "RDF": "" - }, - "2009": { - "CN": "人物原案", - "EN": "Original Character Design", - "JP": "キャラクター原案", - "RDF": "" - } - }, - "2": { - "1": { - "CN": "原作", - "EN": "Original Creator/Original Work", - "JP": "", - "RDF": "" - }, - "2": { - "CN": "导演", - "EN": "Director/Direction", - "JP": "監督 シリーズ監督", - "RDF": "directedBy" - }, - "3": { - "CN": "脚本", - "EN": "Script/Screenplay", - "JP": "シナリオ", - "RDF": "" - }, - "4": { - "CN": "分镜", - "EN": "Storyboard", - "JP": "コンテ ストーリーボード 画コンテ 絵コンテ", - "RDF": "" - }, - "5": { - "CN": "演出", - "EN": "Episode Director", - "JP": "", - "RDF": "" - }, - "6": { - "CN": "音乐", - "EN": "Music", - "JP": "楽曲 音楽", - "RDF": "" - }, - "7": { - "CN": "人物原案", - "EN": "Original Character Design", - "JP": "キャラ原案", - "RDF": "" - }, - "8": { - "CN": "人物设定", - "EN": "Character Design", - "JP": "キャラ設定", - "RDF": "" - }, - "9": { - "CN": "分镜构图", - "EN": "Layout", - "JP": "レイアウト", - "RDF": "" - }, - "10": { - "CN": "系列构成", - "EN": "Series Composition", - "JP": "シナリオディレクター 構成 シリーズ構成 脚本構成", - "RDF": "" - }, - "11": { - "CN": "美术监督", - "EN": "Art Direction", - "JP": "アートディレクション 背景監督", - "RDF": "" - }, - "13": { - "CN": "色彩设计", - "EN": "Color Design", - "JP": "色彩設定", - "RDF": "" - }, - "14": { - "CN": "总作画监督", - "EN": "Chief Animation Director", - "JP": "チーフ作画監督", - "RDF": "" - }, - "15": { - "CN": "作画监督", - "EN": "Animation Direction", - "JP": "作監 アニメーション演出", - "RDF": "" - }, - "16": { - "CN": "机械设定", - "EN": "Mechanical Design", - "JP": "メカニック設定", - "RDF": "" - }, - "17": { - "CN": "摄影监督", - "EN": "Director of Photography", - "JP": "撮影監督", - "RDF": "" - }, - "18": { - "CN": "监修", - "EN": "Supervision/Supervisor", - "JP": "シリーズ監修 スーパーバイザー", - "RDF": "" - }, - "19": { - "CN": "道具设计", - "EN": "Prop Design", - "JP": "プロップデザイン", - "RDF": "" - }, - "20": { - "CN": "原画", - "EN": "Key Animation", - "JP": "作画 原画", - "RDF": "" - }, - "21": { - "CN": "第二原画", - "EN": "2nd Key Animation", - "JP": "原画協力", - "RDF": "" - }, - "22": { - "CN": "动画检查", - "EN": "Animation Check", - "JP": "動画チェック", - "RDF": "" - }, - "23": { - "CN": "助理制片人", - "EN": "Assistant Producer", - "JP": "協力プロデューサー", - "RDF": "" - }, - "24": { - "CN": "", - "EN": "Associate Producer", - "JP": "アソシエイトプロデューサー", - "RDF": "" - }, - "25": { - "CN": "背景美术", - "EN": "Background Art", - "JP": "背景", - "RDF": "" - }, - "26": { - "CN": "色彩指定", - "EN": "Color Setting", - "JP": "", - "RDF": "" - }, - "27": { - "CN": "数码绘图", - "EN": "Digital Paint", - "JP": "", - "RDF": "" - }, - "28": { - "CN": "剪辑", - "EN": "Editing", - "JP": "編集", - "RDF": "" - }, - "29": { - "CN": "原案", - "EN": "Original Plan", - "JP": "", - "RDF": "" - }, - "30": { - "CN": "主题歌编曲", - "EN": "Theme Song Arrangement", - "JP": "", - "RDF": "" - }, - "31": { - "CN": "主题歌作曲", - "EN": "Theme Song Composition", - "JP": "", - "RDF": "" - }, - "32": { - "CN": "主题歌作词", - "EN": "Theme Song Lyrics", - "JP": "", - "RDF": "" - }, - "33": { - "CN": "主题歌演出", - "EN": "Theme Song Performance", - "JP": "", - "RDF": "" - }, - "34": { - "CN": "插入歌演出", - "EN": "Inserted Song Performance", - "JP": "", - "RDF": "" - }, - "35": { - "CN": "企画", - "EN": "Planning", - "JP": "プランニング 企画開発", - "RDF": "" - }, - "36": { - "CN": "", - "EN": "Planning Producer", - "JP": "企画プロデューサー 企画営業プロデューサー", - "RDF": "" - }, - "37": { - "CN": "制作管理", - "EN": "Production Manager", - "JP": "制作マネージャー 制作担当 制作班長", - "RDF": "" - }, - "38": { - "CN": "宣传", - "EN": "Publicity", - "JP": "パブリシティ 宣伝 広告宣伝 番組宣伝 製作宣伝", - "RDF": "" - }, - "39": { - "CN": "录音", - "EN": "Recording", - "JP": "録音", - "RDF": "" - }, - "40": { - "CN": "录音助理", - "EN": "Recording Assistant", - "JP": "録音アシスタント 録音助手", - "RDF": "" - }, - "41": { - "CN": "系列监督", - "EN": "Series Production Director", - "JP": "", - "RDF": "" - }, - "42": { - "CN": "製作", - "EN": "Production", - "JP": "", - "RDF": "" - }, - "43": { - "CN": "设定", - "EN": "Setting", - "JP": "設定", - "RDF": "" - }, - "44": { - "CN": "音响监督", - "EN": "Sound Director", - "JP": "", - "RDF": "" - }, - "45": { - "CN": "音响", - "EN": "Sound", - "JP": "音響", - "RDF": "" - }, - "46": { - "CN": "音效", - "EN": "Sound Effects", - "JP": "音響効果", - "RDF": "" - }, - "47": { - "CN": "特效", - "EN": "Special Effects", - "JP": "視覚効果", - "RDF": "" - }, - "48": { - "CN": "配音监督", - "EN": "ADR Director", - "JP": "", - "RDF": "" - }, - "49": { - "CN": "联合导演", - "EN": "Co-Director", - "JP": "", - "RDF": "" - }, - "50": { - "CN": "背景设定", - "EN": "Setting", - "JP": "基本設定 場面設定 場面設計 設定", - "RDF": "" - }, - "51": { - "CN": "补间动画", - "EN": "In-Between Animation", - "JP": "動画", - "RDF": "" - }, - "52": { - "CN": "执行制片人", - "EN": "Executive Producer", - "JP": "製作総指揮", - "RDF": "" - }, - "53": { - "CN": "助理制片人", - "EN": "Assistant Producer", - "JP": "協力プロデューサー", - "RDF": "" - }, - "54": { - "CN": "制片人", - "EN": "Producer", - "JP": "プロデュース プロデューサー", - "RDF": "" - }, - "55": { - "CN": "助理录音师", - "EN": "Assistant Engineer", - "JP": "", - "RDF": "" - }, - "56": { - "CN": "助理制片协调", - "EN": "Assistant Production Coordinat", - "JP": "", - "RDF": "" - }, - "57": { - "CN": "演员监督", - "EN": "Casting Director", - "JP": "キャスティングコーディネーター監督", - "RDF": "" - }, - "58": { - "CN": "总制片", - "EN": "Chief Producer", - "JP": "チーフプロデューサー チーフ制作 総合プロデューサー", - "RDF": "" - }, - "59": { - "CN": "联合制片人", - "EN": "Co-Producer", - "JP": "", - "RDF": "" - }, - "60": { - "CN": "台词编辑", - "EN": "Dialogue Editing", - "JP": "台詞編集", - "RDF": "" - }, - "61": { - "CN": "后期制片协调", - "EN": "Post-Production Assistant", - "JP": "ポストプロダクション協力", - "RDF": "" - }, - "62": { - "CN": "制作助手", - "EN": "Production Assistant", - "JP": "制作アシスタント 制作補佐 製作補", - "RDF": "" - }, - "63": { - "CN": "制作", - "EN": "Production", - "JP": "製作 製作スタジオ", - "RDF": "" - }, - "64": { - "CN": "制作协调", - "EN": "Production Coordination", - "JP": "制作コーディネーター", - "RDF": "" - }, - "65": { - "CN": "音乐制作", - "EN": "Music Work", - "JP": "楽曲制作 音楽制作", - "RDF": "" - }, - "66": { - "CN": "友情協力", - "EN": "Special Thanks", - "JP": "特别鸣谢", - "RDF": "" - }, - "67": { - "CN": "动画制作", - "EN": "Animation Work", - "JP": "アニメーション制作 アニメ制作 アニメーション", - "RDF": "" - }, - "69": { - "CN": "CG 导演", - "EN": "CG Director", - "JP": "CG 監督", - "RDF": "" - }, - "70": { - "CN": "机械作画监督", - "EN": "Mechanical Animation Direction", - "JP": "メカニック作監", - "RDF": "" - }, - "71": { - "CN": "美术设计", - "EN": "Art Design", - "JP": "美術設定", - "RDF": "" - }, - "72": { - "CN": "副导演", - "EN": "Assistant director", - "JP": "助監督", - "RDF": "" - }, - "73": { - "CN": "OP・ED 分镜", - "EN": "OP ED", - "JP": "OP・ED 分鏡", - "RDF": "" - }, - "74": { - "CN": "总导演", - "EN": "Chief Director", - "JP": "総監督", - "RDF": "" - }, - "75": { - "CN": "3DCG", - "EN": "3DCG", - "JP": "", - "RDF": "" - }, - "76": { - "CN": "制作协力", - "EN": "Work Assistance", - "JP": "制作協力 / 作品協力", - "RDF": "" - }, - "77": { - "CN": "动作作画监督", - "EN": "Action Animation Direction", - "JP": "アクション作画監督", - "RDF": "" - } - }, - "3": { - "3001": { - "CN": "艺术家", - "EN": "", - "JP": "", - "RDF": "" - }, - "3002": { - "CN": "制作人", - "EN": "", - "JP": "", - "RDF": "" - }, - "3003": { - "CN": "作曲", - "EN": "", - "JP": "", - "RDF": "" - }, - "3004": { - "CN": "厂牌", - "EN": "Label", - "JP": "", - "RDF": "" - }, - "3005": { - "CN": "原作", - "EN": "Original Creator/Original Work", - "JP": "", - "RDF": "" - }, - "3006": { - "CN": "作词", - "EN": "Lyric", - "JP": "", - "RDF": "" - }, - "3007": { - "CN": "录音", - "EN": "", - "JP": "", - "RDF": "" - }, - "3008": { - "CN": "编曲", - "EN": "Arrange", - "JP": "", - "RDF": "" - }, - "3009": { - "CN": "插图", - "EN": "", - "JP": "", - "RDF": "" - }, - "3010": { - "CN": "脚本", - "EN": "Scenario", - "JP": "シナリオ", - "RDF": "" - } - }, - "4": { - "1001": { - "CN": "开发", - "EN": "developer", - "JP": "開発元", - "RDF": "" - }, - "1002": { - "CN": "发行", - "EN": "publisher", - "JP": "発売元", - "RDF": "" - }, - "1003": { - "CN": "遊戲設計師", - "EN": "game designer", - "JP": "ゲームクリエイター", - "RDF": "" - }, - "1004": { - "CN": "剧本", - "EN": "", - "JP": "腳本", - "RDF": "" - }, - "1005": { - "CN": "美工", - "EN": "", - "JP": "美術", - "RDF": "" - }, - "1006": { - "CN": "音乐", - "EN": "", - "JP": "音楽", - "RDF": "" - }, - "1007": { - "CN": "关卡设计", - "EN": "", - "JP": "", - "RDF": "" - }, - "1008": { - "CN": "人物设定", - "EN": "Character Design", - "JP": "キャラ設定 キャラクターデザイン", - "RDF": "" - }, - "1009": { - "CN": "主题歌作曲", - "EN": "Theme Song Composition", - "JP": "", - "RDF": "" - }, - "1010": { - "CN": "主题歌作词", - "EN": "Theme Song Lyrics", - "JP": "", - "RDF": "" - }, - "1011": { - "CN": "主题歌演出", - "EN": "Theme Song Performance", - "JP": "", - "RDF": "" - }, - "1012": { - "CN": "插入歌演出", - "EN": "Inserted Song Performance", - "JP": "", - "RDF": "" - }, - "1013": { - "CN": "原画", - "EN": "", - "JP": "", - "RDF": "" - }, - "1014": { - "CN": "动画制作", - "EN": "Animation Work", - "JP": "アニメーション制作 アニメ制作 アニメーション", - "RDF": "" - }, - "1015": { - "CN": "原作", - "EN": "", - "JP": "", - "RDF": "" - }, - "1016": { - "CN": "导演", - "EN": "Director/Direction", - "JP": "監督 演出 シリーズ監督", - "RDF": "" - }, - "1017": { - "CN": "动画监督", - "EN": "", - "JP": "アニメーション監督", - "RDF": "" - }, - "1018": { - "CN": "制作总指挥", - "EN": "", - "JP": "", - "RDF": "" - }, - "1019": { - "CN": "QC", - "EN": "QC", - "JP": "", - "RDF": "" - }, - "1020": { - "CN": "动画剧本", - "EN": "", - "JP": "アニメーション脚本", - "RDF": "" - }, - "1021": { - "CN": "程序", - "EN": "Program", - "JP": "プログラム", - "RDF": "" - }, - "1022": { - "CN": "协力", - "EN": "", - "JP": "協力", - "RDF": "" - }, - "1023": { - "CN": "CG监修", - "EN": "", - "JP": "CG監修", - "RDF": "" - }, - "1024": { - "CN": "SD原画", - "EN": "", - "JP": "", - "RDF": "" - }, - "1025": { - "CN": "背景", - "EN": "", - "JP": "", - "RDF": "" - }, - "1026": { - "CN": "监修", - "EN": "", - "JP": "監修", - "RDF": "" - }, - "1027": { - "CN": "系列构成", - "EN": "", - "JP": "シリーズ構成", - "RDF": "" - }, - "1028": { - "CN": "企画", - "EN": "", - "JP": "", - "RDF": "" - }, - "1029": { - "CN": "机械设定", - "EN": "Mechanical Design", - "JP": "メカニック設定", - "RDF": "" - }, - "1030": { - "CN": "音响监督", - "EN": "Sound Director", - "JP": "", - "RDF": "" - }, - "1031": { - "CN": "作画监督", - "EN": "", - "JP": "作画監督", - "RDF": "" - }, - "1032": { - "CN": "制作人", - "EN": "Producer", - "JP": "プロデューサー", - "RDF": "" - } - }, - "6": { - "4001": { - "CN": "原作", - "EN": "creator", - "JP": "", - "RDF": "" - }, - "4002": { - "CN": "导演", - "EN": "director", - "JP": "", - "RDF": "" - }, - "4003": { - "CN": "编剧", - "EN": "writer", - "JP": "", - "RDF": "" - }, - "4004": { - "CN": "音乐", - "EN": "composer", - "JP": "", - "RDF": "" - }, - "4005": { - "CN": "执行制片人", - "EN": "executive producer", - "JP": "製作総指揮", - "RDF": "" - }, - "4006": { - "CN": "共同执行制作", - "EN": "co exec ", - "JP": "", - "RDF": "" - }, - "4007": { - "CN": "制片人/制作人", - "EN": "producer", - "JP": "プロデューサー", - "RDF": "" - }, - "4008": { - "CN": "监制", - "EN": "supervising producer", - "JP": "", - "RDF": "" - }, - "4009": { - "CN": "副制作人/制作顾问", - "EN": "consulting producer", - "JP": "", - "RDF": "" - }, - "4010": { - "CN": "故事", - "EN": "story", - "JP": "", - "RDF": "" - }, - "4011": { - "CN": "编审", - "EN": "story editor", - "JP": "", - "RDF": "" - }, - "4012": { - "CN": "剪辑", - "EN": "editor", - "JP": "", - "RDF": "" - }, - "4013": { - "CN": "创意总监", - "EN": "creative director", - "JP": "", - "RDF": "" - }, - "4014": { - "CN": "摄影", - "EN": "cinematography", - "JP": "", - "RDF": "" - }, - "4015": { - "CN": "主题歌演出", - "EN": "Theme Song Performance", - "JP": "", - "RDF": "" - }, - "4016": { - "CN": "主演", - "EN": "Actor", - "JP": "", - "RDF": "" - }, - "4017": { - "CN": "配角", - "EN": "Supporting Actor", - "JP": "", - "RDF": "" - }, - "4018": { - "CN": "制作", - "EN": "Production", - "JP": "製作 製作スタジオ", - "RDF": "" - } - } -} diff --git a/pkg/vars/staffs.go.json b/pkg/vars/staffs.go.json new file mode 100644 index 000000000..317b84880 --- /dev/null +++ b/pkg/vars/staffs.go.json @@ -0,0 +1,6082 @@ +{ + "define": { + "type": { + "book": 1, + "anime": 2, + "music": 3, + "game": 4, + "real": 6 + }, + "categories": { + "anime": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + }, + { + "order": 3, + "en": "direction", + "cn": "演出类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + }, + { + "order": 5, + "en": "design", + "cn": "设定类" + }, + { + "order": 6, + "en": "art", + "cn": "美术类" + }, + { + "order": 7, + "en": "music", + "cn": "声音类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + }, + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 10, + "en": "colorist", + "cn": "色彩类" + }, + { + "order": 11, + "en": "visual", + "cn": "视觉类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ], + "game": [ + { + "order": 1, + "en": "producer", + "cn": "发行类" + }, + { + "order": 2, + "en": "director", + "cn": "导演类" + }, + { + "order": 3, + "en": "storyboard", + "cn": "脚本类" + }, + { + "order": 4, + "en": "design", + "cn": "设定类" + }, + { + "order": 5, + "en": "art", + "cn": "美术类" + }, + { + "order": 6, + "en": "animation", + "cn": "动画类" + }, + { + "order": 7, + "en": "music", + "cn": "声音类" + }, + { + "order": 8, + "en": "production", + "cn": "制作/程序类" + }, + { + "order": 0, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "types": { + "anime": { + "1": { + "en": "Original Creator/Original Work", + "cn": "原作", + "jp": "", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "2": { + "en": "Director/Direction", + "cn": "导演", + "jp": "監督 シリーズ監督", + "rdf": "directedBy", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "3": { + "en": "Script/Screenplay", + "cn": "脚本", + "jp": "シナリオ", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + } + ] + }, + "4": { + "en": "Storyboard", + "cn": "分镜", + "jp": "コンテ ストーリーボード 画コンテ 絵コンテ", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + } + ] + }, + "5": { + "en": "Episode Direction", + "cn": "演出", + "jp": "", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + } + ] + }, + "6": { + "en": "Music", + "cn": "音乐", + "jp": "楽曲 音楽", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "7": { + "en": "Original Character Design", + "cn": "人物原案", + "jp": "キャラ原案", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "8": { + "en": "Character Design", + "cn": "人物设定", + "jp": "キャラ設定", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "9": { + "en": "Layout", + "cn": "构图", + "jp": "レイアウト", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + } + ] + }, + "10": { + "en": "Series Composition", + "cn": "系列构成", + "jp": "シナリオディレクター 構成 シリーズ構成 脚本構成", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + } + ] + }, + "11": { + "en": "Art Direction", + "cn": "美术监督", + "jp": "美術監督 アートディレクション 背景監督", + "categories": [ + { + "order": 6, + "en": "art", + "cn": "美术类" + }, + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "13": { + "en": "Color Design", + "cn": "色彩设计", + "jp": "色彩設定", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + }, + { + "order": 10, + "en": "colorist", + "cn": "色彩类" + } + ] + }, + "14": { + "en": "Chief Animation Director", + "cn": "总作画监督", + "jp": "チーフ作画監督", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "15": { + "en": "Animation Direction", + "cn": "作画监督", + "jp": "作監 アニメーション演出", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "16": { + "en": "Mechanical Design", + "cn": "机械设定", + "jp": "メカニック設定", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "17": { + "en": "Director of Photography", + "cn": "摄影监督", + "jp": "撮影監督", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "18": { + "en": "Supervision/Supervisor", + "cn": "监修", + "jp": "シリーズ監修 スーパーバイザー", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "19": { + "en": "Prop Design", + "cn": "道具设计", + "jp": "プロップデザイン", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "20": { + "en": "Key Animation", + "cn": "原画", + "jp": "作画 原画", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "21": { + "en": "2nd Key Animation", + "cn": "第二原画", + "jp": "原画協力", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "22": { + "en": "Animation Check", + "cn": "动画检查", + "jp": "動画チェック", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "23": { + "en": "Assistant Producer", + "cn": "助理制片人", + "jp": "協力プロデューサー(⚠️ 待合并)" + }, + "24": { + "en": "Associate Producer", + "cn": "制作助理", + "jp": "製作補佐 アソシエイトプロデューサー", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "25": { + "en": "Background Art", + "cn": "背景美术", + "jp": "背景", + "categories": [ + { + "order": 6, + "en": "art", + "cn": "美术类" + }, + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "26": { + "en": "Color Setting", + "cn": "色彩指定", + "jp": "", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + }, + { + "order": 10, + "en": "colorist", + "cn": "色彩类" + } + ] + }, + "27": { + "en": "Digital Paint", + "cn": "数码绘图", + "jp": "", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "28": { + "en": "Editing", + "cn": "剪辑", + "jp": "編集", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "29": { + "en": "Original Plan", + "cn": "原案", + "jp": "", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + } + ] + }, + "30": { + "en": "Theme Song Arrangement", + "cn": "主题歌编曲", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "31": { + "en": "Theme Song Composition", + "cn": "主题歌作曲", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "32": { + "en": "Theme Song Lyrics", + "cn": "主题歌作词", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "33": { + "en": "Theme Song Performance", + "cn": "主题歌演出", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "34": { + "en": "Inserted Song Performance", + "cn": "插入歌演出", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "35": { + "en": "Planning", + "cn": "企画", + "jp": "プランニング 企画開発", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "36": { + "en": "Planning Producer", + "cn": "企划制作人", + "jp": "企画プロデューサー 企画営業プロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + } + ] + }, + "37": { + "en": "Production Manager", + "cn": "制作管理", + "jp": "制作マネージャー 制作担当 制作班長", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "38": { + "en": "Publicity", + "cn": "宣传", + "jp": "パブリシティ 宣伝 広告宣伝 番組宣伝 製作宣伝", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "39": { + "en": "Recording", + "cn": "录音", + "jp": "録音", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "40": { + "en": "Recording Assistant", + "cn": "录音助理", + "jp": "録音アシスタント 録音助手", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "41": { + "en": "Series Production Director", + "cn": "系列监督", + "jp": "", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "42": { + "en": "Production", + "cn": "製作", + "jp": "", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "43": { + "en": "Setting", + "cn": "设定", + "jp": "設定", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "44": { + "en": "Sound Director", + "cn": "音响监督", + "jp": "", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "45": { + "en": "Sound", + "cn": "音响", + "jp": "音響 音声", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "46": { + "en": "Sound Effects", + "cn": "音效", + "jp": "音響効果", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "47": { + "en": "Special Effects", + "cn": "特效", + "jp": "特殊効果", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + }, + { + "order": 11, + "en": "visual", + "cn": "视觉类" + } + ] + }, + "48": { + "en": "ADR Director", + "cn": "配音监督", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + }, + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "49": { + "en": "Co-Director", + "cn": "联合导演", + "jp": "", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "50": { + "en": "Setting", + "cn": "背景设定", + "jp": "基本設定 場面設定 場面設計 設定", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "51": { + "en": "In-Between Animation", + "cn": "补间动画", + "jp": "動画", + "categories": [ + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "52": { + "en": "Executive Producer", + "cn": "执行制片人", + "jp": "製作総指揮", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + } + ] + }, + "53": { + "en": "Assistant Producer", + "cn": "助理制片人", + "jp": "協力プロデューサー アシスタントプロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "54": { + "en": "Producer", + "cn": "制片人", + "jp": "プロデュース プロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + } + ] + }, + "55": { + "en": "Music Assistant", + "cn": "音乐助理", + "jp": "音楽アシスタント", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "56": { + "en": "Assistant Production Manager", + "cn": "制作进行", + "jp": "制作進行", + "desc": "管理动画的制作时程、协调各部门作业、回收作画原稿等", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "57": { + "en": "Casting Director", + "cn": "演员监督", + "jp": "キャスティングコーディネーター監督", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "58": { + "en": "Chief Producer", + "cn": "总制片人", + "jp": "チーフプロデューサー チーフ制作 総合プロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + } + ] + }, + "59": { + "en": "Co-Producer", + "cn": "联合制片人", + "jp": "", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + } + ] + }, + "60": { + "en": "Dialogue Editing", + "cn": "台词编辑", + "jp": "台詞編集", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "61": { + "en": "Post-Production Assistant", + "cn": "后期制片协调", + "jp": "ポストプロダクション協力", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "62": { + "en": "Production Assistant", + "cn": "制作助理", + "jp": "制作アシスタント 制作補佐 製作補", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "63": { + "en": "Production", + "cn": "制作", + "jp": "製作 製作スタジオ", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "64": { + "en": "Production Coordination", + "cn": "制作协调", + "jp": "制作コーディネーター", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "65": { + "en": "Music Work", + "cn": "音乐制作", + "jp": "楽曲制作 音楽制作", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "66": { + "en": "Special Thanks", + "cn": "特别鸣谢", + "jp": "友情協力", + "categories": [ + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "67": { + "en": "Animation Work", + "cn": "动画制作", + "jp": "アニメーション制作 アニメ制作 アニメーション", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "69": { + "en": "CG Director", + "cn": "CG 导演", + "jp": "CG 監督", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "70": { + "en": "Mechanical Animation Direction", + "cn": "机械作画监督", + "jp": "メカニック作監", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "71": { + "en": "Art Design", + "cn": "美术设计", + "jp": "美術設定", + "categories": [ + { + "order": 6, + "en": "art", + "cn": "美术类" + }, + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "72": { + "en": "Assistant Director", + "cn": "副导演", + "jp": "助監督 / 監督補佐", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "73": { + "en": "OP ED", + "cn": "OP・ED 分镜", + "jp": "OP・ED 分鏡", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + } + ] + }, + "74": { + "en": "Chief Director", + "cn": "总导演", + "jp": "総監督 / チーフディレクター", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "75": { + "en": "3DCG", + "cn": "3DCG", + "jp": "", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "76": { + "en": "Work Assistance", + "cn": "制作协力", + "jp": "制作協力 / 作品協力", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "77": { + "en": "Action Animation Direction", + "cn": "动作作画监督", + "jp": "アクション作画監督", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "80": { + "en": "Supervising Producer", + "cn": "监制", + "jp": "プロデュース", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + } + ] + }, + "81": { + "en": "Assistance", + "cn": "协力", + "jp": "協力", + "categories": [ + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "82": { + "en": "Photography", + "cn": "摄影", + "jp": "撮影", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "83": { + "en": "Assistant Production Manager Assistance", + "cn": "制作进行协力", + "jp": "制作進行協力", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "84": { + "en": "Design Manager", + "cn": "设定制作", + "jp": "設定制作 制作設定", + "desc": "有时需要额外的设计工作,联系负责部门并监督工作确保交付", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "85": { + "en": "Music Producer", + "cn": "音乐制作人", + "jp": "音楽プロデューサー", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "86": { + "en": "3DCG Director", + "cn": "3DCG 导演", + "jp": "3DCG 監督", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "87": { + "en": "Animation Producer", + "cn": "动画制片人", + "jp": "アニメプロデューサー アニメーションプロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + } + ] + }, + "88": { + "en": "Special Effects Animation Direction", + "cn": "特效作画监督", + "jp": "エフェクト作画監督", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + }, + { + "order": 11, + "en": "visual", + "cn": "视觉类" + } + ] + }, + "89": { + "en": "Chief Episode Direction", + "cn": "主演出", + "jp": "チーフ演出", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + } + ] + }, + "90": { + "en": "Assistant Animation Direction", + "cn": "作画监督助理", + "jp": "作画監督補佐", + "categories": [ + { + "order": 12, + "en": "assistant", + "cn": "助理类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "91": { + "en": "Assistant Episode Direction", + "cn": "演出助理", + "jp": "演出助手 演出補佐 演出協力", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "92": { + "en": "Main Animator", + "cn": "主动画师", + "jp": "メインアニメーター", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "93": { + "en": "", + "cn": "上色", + "jp": "仕上", + "categories": [ + { + "order": 10, + "en": "colorist", + "cn": "色彩类" + } + ] + }, + "94": { + "en": "", + "cn": "上色检查", + "jp": "仕上検查", + "categories": [ + { + "order": 10, + "en": "colorist", + "cn": "色彩类" + } + ] + }, + "95": { + "en": "", + "cn": "色彩检查", + "jp": "色検查", + "categories": [ + { + "order": 10, + "en": "colorist", + "cn": "色彩类" + } + ] + }, + "96": { + "en": "", + "cn": "美术板", + "jp": "美術ボード", + "categories": [ + { + "order": 6, + "en": "art", + "cn": "美术类" + } + ] + }, + "97": { + "en": "", + "cn": "美术", + "jp": "美術", + "categories": [ + { + "order": 6, + "en": "art", + "cn": "美术类" + } + ] + }, + "98": { + "en": "", + "cn": "印象板", + "jp": "イメージボード", + "categories": [ + { + "order": 6, + "en": "art", + "cn": "美术类" + }, + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "99": { + "en": "2D WORKS", + "cn": "2D 设计", + "jp": "2D ワークス", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "100": { + "en": "3D WORKS", + "cn": "3D 设计", + "jp": "3D ワークス", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "101": { + "en": "Technical Director", + "cn": "技术导演", + "jp": "テクニカルディレクター", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "102": { + "en": "", + "cn": "特技导演", + "jp": "特撮監督", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "103": { + "en": "Color Script", + "cn": "色彩脚本", + "jp": "カラースクリプト", + "categories": [ + { + "order": 10, + "en": "colorist", + "cn": "色彩类" + } + ] + }, + "104": { + "en": "", + "cn": "分镜协力", + "jp": "絵コンテ協力 コンテ協力", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "105": { + "en": "", + "cn": "分镜抄写", + "jp": "絵コンテ清書 コンテ清書", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "106": { + "en": "Sub-Character Design", + "cn": "副人物设定", + "jp": "サブキャラクターデザイン", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "107": { + "en": "Guest character design", + "cn": "客座人物设定", + "jp": "ゲストキャラクターデザイン", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "108": { + "en": "", + "cn": "构图监修", + "jp": "レイアウト監修", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + } + ] + }, + "109": { + "en": "", + "cn": "构图作画监督", + "jp": "レイアウト作画監督 レイアウト作監", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "110": { + "en": "", + "cn": "总作画监督助理", + "jp": "総作画監督補佐 総作監補佐", + "categories": [ + { + "order": 12, + "en": "assistant", + "cn": "助理类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "111": { + "en": "", + "cn": "道具作画监督", + "jp": "プロップ作画監督 プロップ作監", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "112": { + "en": "Concept Design", + "cn": "概念设计", + "jp": "コンセプトデザイン", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "113": { + "en": "Costume Design", + "cn": "服装设计", + "jp": "衣装デザイン 衣装設定", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "114": { + "en": "Title Design", + "cn": "标题设计", + "jp": "タイトルデザイン", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "115": { + "en": "Setting Cooperation", + "cn": "设定协力", + "jp": "設定協力 デザイン協力", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "116": { + "en": "Music Director", + "cn": "音乐监督", + "jp": "音楽ディレクター", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + }, + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "117": { + "en": "Music Selection", + "cn": "选曲", + "jp": "選曲", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "118": { + "en": "Inserted Song Lyrics", + "cn": "插入歌作词", + "jp": "Insert Song Lyrics", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "119": { + "en": "Inserted Song Composition", + "cn": "插入歌作曲", + "jp": "Insert Song Composition", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "120": { + "en": "Inserted Song Arrangement", + "cn": "插入歌编曲", + "jp": "Insert Song Arrangement", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "121": { + "en": "Creative Producer", + "cn": "创意制片人", + "jp": "クリエイティブプロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + } + ] + }, + "122": { + "en": "Associate Producer", + "cn": "副制片人", + "jp": "アソシエイトプロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "123": { + "en": "Chief Production Supervisor", + "cn": "制作统括", + "jp": "制作統括", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "124": { + "en": "Line Producer", + "cn": "现场制片人", + "jp": "ラインプロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "125": { + "en": "Literary Producer", + "cn": "文艺制作", + "jp": "文芸制作", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + } + ] + }, + "127": { + "en": "Planning Cooperation", + "cn": "企画协力", + "jp": "企画協力", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "128": { + "en": "OP・ED Direction", + "cn": "OP・ED 演出", + "jp": "OP・ED 演出", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + } + ] + }, + "129": { + "en": "Bank Storyboard Direction", + "cn": "Bank 分镜演出", + "jp": "バンク コンテ・演出", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + } + ] + }, + "130": { + "en": "Live Storyboard Direction", + "cn": "Live 分镜演出", + "jp": "ライブ コンテ・演出", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + } + ] + }, + "131": { + "en": "Meta-story Storyboard Direction", + "cn": "剧中剧分镜演出", + "jp": "劇中劇 コンテ・演出", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + } + ] + }, + "132": { + "en": "Meta-story Character Design", + "cn": "剧中剧人设", + "jp": "劇中劇 キャラクターデザイン", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "133": { + "en": "Visual Director", + "cn": "视觉导演", + "jp": "ビジュアルディレクター", + "categories": [ + { + "order": 11, + "en": "visual", + "cn": "视觉类" + }, + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "134": { + "en": "Creative Supervisor/Director", + "cn": "创意总监", + "jp": "クリエイティブスーパーバイザー クリエイティブディレクター", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "135": { + "en": "Tokusatsu Effects", + "cn": "特摄效果", + "jp": "特撮", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + }, + { + "order": 11, + "en": "visual", + "cn": "视觉类" + } + ] + }, + "136": { + "en": "Visual Effects", + "cn": "视觉效果", + "jp": "ビジュアルエフェクト", + "categories": [ + { + "order": 11, + "en": "visual", + "cn": "视觉类" + } + ] + }, + "137": { + "en": "", + "cn": "动作导演", + "jp": "アクション監督", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "138": { + "en": "Eyecatch Art", + "cn": "转场绘", + "jp": "アイキャッチ", + "categories": [ + { + "order": 4, + "en": "animation", + "cn": "作画类" + }, + { + "order": 11, + "en": "visual", + "cn": "视觉类" + } + ] + }, + "139": { + "en": "Illustration", + "cn": "插画", + "jp": "イラスト", + "categories": [ + { + "order": 4, + "en": "animation", + "cn": "作画类" + }, + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "140": { + "en": "Character Animation Director", + "cn": "角色作画监督", + "jp": "キャラクター作画監督", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "141": { + "en": "Animation Supervisor", + "cn": "作画监修", + "jp": "作画監修", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "142": { + "en": "Mechanical Design Concept", + "cn": "机设原案", + "jp": "メカニカル原案", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "143": { + "en": "Concept Art", + "cn": "概念艺术", + "jp": "コンセプトアート", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "144": { + "en": "Visual Concept", + "cn": "视觉概念", + "jp": "ビジュアルコンセプト", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + }, + { + "order": 11, + "en": "visual", + "cn": "视觉类" + } + ] + }, + "145": { + "en": "Scene Design", + "cn": "画面设计", + "jp": "画面設計", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + }, + { + "order": 11, + "en": "visual", + "cn": "视觉类" + } + ] + }, + "146": { + "en": "Monster Design", + "cn": "怪物设计", + "jp": "モンスターデザイン", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "147": { + "en": "Story Concept", + "cn": "故事概念", + "jp": "ストーリーコンセプト", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + } + ] + }, + "148": { + "en": "Scenario Coordinator", + "cn": "剧本协调", + "jp": "シナリオコーディネーター", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + } + ] + }, + "149": { + "en": "Script Cooperation", + "cn": "脚本协力", + "jp": "脚本協力", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "150": { + "en": "Associate Series Composition", + "cn": "副系列构成", + "jp": "副シリーズ構成", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + } + ] + }, + "151": { + "en": "Series Composition Cooperation", + "cn": "构成协力", + "jp": "構成協力", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "152": { + "en": "Recording Studio", + "cn": "录音工作室", + "jp": "録音スタジオ", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "153": { + "en": "Sound Mixing", + "cn": "整音", + "jp": "整音", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "154": { + "en": "Sound Production Coordinator", + "cn": "音响制作担当", + "jp": "音響制作担当", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "155": { + "en": "Online Editing", + "cn": "在线剪辑", + "jp": "オンライン編集", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "156": { + "en": "Offline Editing", + "cn": "离线剪辑", + "jp": "オフライン編集", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "157": { + "en": "3D Animator", + "cn": "3D 动画师", + "jp": "3Dアニメーター 3Dアニメーション", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "158": { + "en": "CG Producer", + "cn": "CG 制作人", + "jp": "CGプロデューサー CGIプロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "159": { + "en": "Publicity Producer", + "cn": "宣传制片人", + "jp": "宣伝プロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "160": { + "en": "Art Producer", + "cn": "美术制作人", + "jp": "美術プロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 6, + "en": "art", + "cn": "美术类" + } + ] + }, + "161": { + "en": "Sound Producer", + "cn": "音响制作人", + "jp": "音響プロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "162": { + "en": "CG Production Coordinator", + "cn": "CG 制作进行", + "jp": "CG進行", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "163": { + "en": "Art Production Coordinator", + "cn": "美术制作进行", + "jp": "美術進行", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 6, + "en": "art", + "cn": "美术类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "164": { + "en": "Assistant Art Director", + "cn": "美术监督助理", + "jp": "美術監督補佐", + "categories": [ + { + "order": 12, + "en": "assistant", + "cn": "助理类" + }, + { + "order": 6, + "en": "art", + "cn": "美术类" + } + ] + }, + "165": { + "en": "Assistant Color Designer", + "cn": "色彩设计助理", + "jp": "色彩設計補佐", + "categories": [ + { + "order": 12, + "en": "assistant", + "cn": "助理类" + }, + { + "order": 10, + "en": "colorist", + "cn": "色彩类" + } + ] + }, + "166": { + "en": "Assistant Director of Photography", + "cn": "摄影监督助理", + "jp": "撮影監督補佐", + "categories": [ + { + "order": 12, + "en": "assistant", + "cn": "助理类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "167": { + "en": "Assistant Production Desk", + "cn": "制作管理助理", + "jp": "制作デスク補佐", + "categories": [ + { + "order": 12, + "en": "assistant", + "cn": "助理类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "168": { + "en": "Assistant Design Manager", + "cn": "设定制作助理", + "jp": "設定制作補佐", + "categories": [ + { + "order": 12, + "en": "assistant", + "cn": "助理类" + }, + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + } + }, + "game": { + "1001": { + "en": "Developer", + "cn": "开发", + "jp": "開発元", + "categories": [ + { + "order": 1, + "en": "producer", + "cn": "发行类" + } + ] + }, + "1002": { + "en": "Publisher", + "cn": "发行", + "jp": "発売元", + "categories": [ + { + "order": 1, + "en": "producer", + "cn": "发行类" + } + ] + }, + "1003": { + "en": "Game Designer", + "cn": "游戏设计师", + "jp": "ゲームクリエイター", + "categories": [ + { + "order": 2, + "en": "director", + "cn": "导演类" + } + ] + }, + "1004": { + "en": "", + "cn": "剧本", + "jp": "腳本", + "categories": [ + { + "order": 3, + "en": "storyboard", + "cn": "脚本类" + } + ] + }, + "1005": { + "en": "", + "cn": "美工", + "jp": "美術", + "categories": [ + { + "order": 5, + "en": "art", + "cn": "美术类" + } + ] + }, + "1006": { + "en": "", + "cn": "音乐", + "jp": "音楽", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "1007": { + "en": "", + "cn": "关卡设计", + "jp": "", + "categories": [ + { + "order": 4, + "en": "design", + "cn": "设定类" + } + ] + }, + "1008": { + "en": "Character Design", + "cn": "人物设定", + "jp": "キャラ設定 キャラクターデザイン", + "categories": [ + { + "order": 4, + "en": "design", + "cn": "设定类" + } + ] + }, + "1009": { + "en": "Theme Song Composition", + "cn": "主题歌作曲", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "1010": { + "en": "Theme Song Lyrics", + "cn": "主题歌作词", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "1011": { + "en": "Theme Song Performance", + "cn": "主题歌演出", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "1012": { + "en": "Inserted Song Performance", + "cn": "插入歌演出", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "1013": { + "en": "", + "cn": "原画", + "jp": "", + "categories": [ + { + "order": 5, + "en": "art", + "cn": "美术类" + } + ] + }, + "1014": { + "en": "Animation Work", + "cn": "动画制作", + "jp": "アニメーション制作 アニメ制作 アニメーション", + "categories": [ + { + "order": 6, + "en": "animation", + "cn": "动画类" + } + ] + }, + "1015": { + "en": "", + "cn": "原作", + "jp": "", + "categories": [ + { + "order": 4, + "en": "design", + "cn": "设定类" + } + ] + }, + "1016": { + "en": "Director/Direction", + "cn": "导演", + "jp": "監督 演出 シリーズ監督", + "categories": [ + { + "order": 2, + "en": "director", + "cn": "导演类" + } + ] + }, + "1017": { + "en": "", + "cn": "动画监督", + "jp": "アニメーション監督", + "categories": [ + { + "order": 6, + "en": "animation", + "cn": "动画类" + } + ] + }, + "1018": { + "en": "", + "cn": "制作总指挥", + "jp": "", + "categories": [ + { + "order": 2, + "en": "director", + "cn": "导演类" + } + ] + }, + "1019": { + "en": "QC", + "cn": "QC", + "jp": "", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作/程序类" + } + ] + }, + "1020": { + "en": "", + "cn": "动画剧本", + "jp": "アニメーション脚本", + "categories": [ + { + "order": 6, + "en": "animation", + "cn": "动画类" + } + ] + }, + "1021": { + "en": "Program", + "cn": "程序", + "jp": "プログラム", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作/程序类" + } + ] + }, + "1022": { + "en": "", + "cn": "协力", + "jp": "協力", + "categories": [ + { + "order": 0, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "1023": { + "en": "", + "cn": "CG 监修", + "jp": "CG 監修", + "categories": [ + { + "order": 5, + "en": "art", + "cn": "美术类" + } + ] + }, + "1024": { + "en": "", + "cn": "SD原画", + "jp": "", + "categories": [ + { + "order": 5, + "en": "art", + "cn": "美术类" + } + ] + }, + "1025": { + "en": "", + "cn": "背景", + "jp": "", + "categories": [ + { + "order": 5, + "en": "art", + "cn": "美术类" + } + ] + }, + "1026": { + "en": "", + "cn": "监修", + "jp": "監修", + "categories": [ + { + "order": 2, + "en": "director", + "cn": "导演类" + } + ] + }, + "1027": { + "en": "", + "cn": "系列构成", + "jp": "シリーズ構成", + "categories": [ + { + "order": 3, + "en": "storyboard", + "cn": "脚本类" + } + ] + }, + "1028": { + "en": "", + "cn": "企画", + "jp": "", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作/程序类" + } + ] + }, + "1029": { + "en": "Mechanical Design", + "cn": "机械设定", + "jp": "メカニック設定", + "categories": [ + { + "order": 4, + "en": "design", + "cn": "设定类" + } + ] + }, + "1030": { + "en": "Sound Director", + "cn": "音响监督", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "1031": { + "en": "", + "cn": "作画监督", + "jp": "作画監督", + "categories": [ + { + "order": 5, + "en": "art", + "cn": "美术类" + } + ] + }, + "1032": { + "en": "Producer", + "cn": "制作人", + "jp": "プロデューサー", + "categories": [ + { + "order": 2, + "en": "director", + "cn": "导演类" + } + ] + }, + "1033": { + "en": "Cover Art", + "cn": "海报", + "jp": "表紙", + "categories": [ + { + "order": 5, + "en": "art", + "cn": "美术类" + } + ] + } + }, + "book": { + "2001": { + "en": "", + "cn": "作者", + "jp": "" + }, + "2002": { + "en": "", + "cn": "作画", + "jp": "" + }, + "2003": { + "en": "", + "cn": "插图", + "jp": "イラスト" + }, + "2004": { + "en": "", + "cn": "出版社", + "jp": "" + }, + "2005": { + "en": "", + "cn": "连载杂志", + "jp": "掲載誌" + }, + "2006": { + "en": "", + "cn": "译者", + "jp": "" + }, + "2007": { + "en": "Original Creator/Original Work", + "cn": "原作", + "jp": "" + }, + "2008": { + "en": "Guest", + "cn": "客串", + "jp": "ゲスト" + }, + "2009": { + "en": "Original Character Design", + "cn": "人物原案", + "jp": "キャラクター原案" + }, + "2010": { + "en": "", + "cn": "脚本", + "jp": "シナリオ" + }, + "2011": { + "en": "Label", + "cn": "书系", + "jp": "" + }, + "2012": { + "en": "", + "cn": "出品方", + "jp": "" + }, + "2013": { + "en": "Brand", + "cn": "图书品牌", + "jp": "ブランド" + } + }, + "music": { + "3001": { + "en": "Artist", + "cn": "艺术家", + "jp": "" + }, + "3002": { + "en": "Producer", + "cn": "制作人", + "jp": "" + }, + "3003": { + "en": "Composer", + "cn": "作曲", + "jp": "" + }, + "3004": { + "en": "Label", + "cn": "厂牌", + "jp": "レーベル" + }, + "3005": { + "en": "Original Creator/Original Work", + "cn": "原作", + "jp": "" + }, + "3006": { + "en": "Lyric", + "cn": "作词", + "jp": "" + }, + "3007": { + "en": "Recording", + "cn": "录音", + "jp": "" + }, + "3008": { + "en": "Arrange", + "cn": "编曲", + "jp": "" + }, + "3009": { + "en": "Illustrator", + "cn": "插图", + "jp": "" + }, + "3010": { + "en": "Scenario", + "cn": "脚本", + "jp": "シナリオ" + }, + "3011": { + "en": "O.P.", + "cn": "出版方", + "jp": "音楽出版社" + }, + "3012": { + "en": "Mastering", + "cn": "母带制作", + "jp": "" + }, + "3013": { + "en": "Mixing", + "cn": "混音", + "jp": "" + }, + "3014": { + "en": "Instrument", + "cn": "乐器", + "jp": "" + }, + "3015": { + "en": "Vocal", + "cn": "声乐", + "jp": "" + } + }, + "real": { + "4001": { + "en": "Creator", + "cn": "原作", + "jp": "" + }, + "4002": { + "en": "Director", + "cn": "导演", + "jp": "" + }, + "4003": { + "en": "Writer", + "cn": "编剧", + "jp": "" + }, + "4004": { + "en": "Composer", + "cn": "音乐", + "jp": "" + }, + "4005": { + "en": "Executive Producer", + "cn": "执行制片人", + "jp": "製作総指揮" + }, + "4006": { + "en": "Co-Executive Producer", + "cn": "共同执行制作", + "jp": "" + }, + "4007": { + "en": "Producer", + "cn": "制片人/制作人", + "jp": "プロデューサー" + }, + "4008": { + "en": "Supervising Producer", + "cn": "监制", + "jp": "" + }, + "4009": { + "en": "Consulting Producer", + "cn": "副制作人/制作顾问", + "jp": "" + }, + "4010": { + "en": "Story", + "cn": "故事", + "jp": "" + }, + "4011": { + "en": "Story Editor", + "cn": "编审", + "jp": "" + }, + "4012": { + "en": "Editor", + "cn": "剪辑", + "jp": "" + }, + "4013": { + "en": "Creative Director", + "cn": "创意总监", + "jp": "" + }, + "4014": { + "en": "Cinematography", + "cn": "摄影", + "jp": "" + }, + "4015": { + "en": "Theme Song Performance", + "cn": "主题歌演出", + "jp": "" + }, + "4016": { + "en": "Actor", + "cn": "主演", + "jp": "" + }, + "4017": { + "en": "Supporting Actor", + "cn": "配角", + "jp": "" + }, + "4018": { + "en": "Production", + "cn": "制作", + "jp": "製作 製作スタジオ" + }, + "4019": { + "en": "Present", + "cn": "出品", + "jp": "配給" + }, + "4020": { + "en": "ADR Director", + "cn": "配音导演", + "jp": "" + }, + "4021": { + "en": "Recording", + "cn": "录音", + "jp": "録音" + }, + "4022": { + "en": "Poster", + "cn": "海报", + "jp": "" + } + } + } + }, + "staffs": { + "1": { + "2001": { + "en": "", + "cn": "作者", + "jp": "" + }, + "2002": { + "en": "", + "cn": "作画", + "jp": "" + }, + "2003": { + "en": "", + "cn": "插图", + "jp": "イラスト" + }, + "2004": { + "en": "", + "cn": "出版社", + "jp": "" + }, + "2005": { + "en": "", + "cn": "连载杂志", + "jp": "掲載誌" + }, + "2006": { + "en": "", + "cn": "译者", + "jp": "" + }, + "2007": { + "en": "Original Creator/Original Work", + "cn": "原作", + "jp": "" + }, + "2008": { + "en": "Guest", + "cn": "客串", + "jp": "ゲスト" + }, + "2009": { + "en": "Original Character Design", + "cn": "人物原案", + "jp": "キャラクター原案" + }, + "2010": { + "en": "", + "cn": "脚本", + "jp": "シナリオ" + }, + "2011": { + "en": "Label", + "cn": "书系", + "jp": "" + }, + "2012": { + "en": "", + "cn": "出品方", + "jp": "" + }, + "2013": { + "en": "Brand", + "cn": "图书品牌", + "jp": "ブランド" + } + }, + "2": { + "1": { + "en": "Original Creator/Original Work", + "cn": "原作", + "jp": "", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "2": { + "en": "Director/Direction", + "cn": "导演", + "jp": "監督 シリーズ監督", + "rdf": "directedBy", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "3": { + "en": "Script/Screenplay", + "cn": "脚本", + "jp": "シナリオ", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + } + ] + }, + "4": { + "en": "Storyboard", + "cn": "分镜", + "jp": "コンテ ストーリーボード 画コンテ 絵コンテ", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + } + ] + }, + "5": { + "en": "Episode Direction", + "cn": "演出", + "jp": "", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + } + ] + }, + "6": { + "en": "Music", + "cn": "音乐", + "jp": "楽曲 音楽", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "7": { + "en": "Original Character Design", + "cn": "人物原案", + "jp": "キャラ原案", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "8": { + "en": "Character Design", + "cn": "人物设定", + "jp": "キャラ設定", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "9": { + "en": "Layout", + "cn": "构图", + "jp": "レイアウト", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + } + ] + }, + "10": { + "en": "Series Composition", + "cn": "系列构成", + "jp": "シナリオディレクター 構成 シリーズ構成 脚本構成", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + } + ] + }, + "11": { + "en": "Art Direction", + "cn": "美术监督", + "jp": "美術監督 アートディレクション 背景監督", + "categories": [ + { + "order": 6, + "en": "art", + "cn": "美术类" + }, + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "13": { + "en": "Color Design", + "cn": "色彩设计", + "jp": "色彩設定", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + }, + { + "order": 10, + "en": "colorist", + "cn": "色彩类" + } + ] + }, + "14": { + "en": "Chief Animation Director", + "cn": "总作画监督", + "jp": "チーフ作画監督", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "15": { + "en": "Animation Direction", + "cn": "作画监督", + "jp": "作監 アニメーション演出", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "16": { + "en": "Mechanical Design", + "cn": "机械设定", + "jp": "メカニック設定", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "17": { + "en": "Director of Photography", + "cn": "摄影监督", + "jp": "撮影監督", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "18": { + "en": "Supervision/Supervisor", + "cn": "监修", + "jp": "シリーズ監修 スーパーバイザー", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "19": { + "en": "Prop Design", + "cn": "道具设计", + "jp": "プロップデザイン", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "20": { + "en": "Key Animation", + "cn": "原画", + "jp": "作画 原画", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "21": { + "en": "2nd Key Animation", + "cn": "第二原画", + "jp": "原画協力", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "22": { + "en": "Animation Check", + "cn": "动画检查", + "jp": "動画チェック", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "23": { + "en": "Assistant Producer", + "cn": "助理制片人", + "jp": "協力プロデューサー(⚠️ 待合并)" + }, + "24": { + "en": "Associate Producer", + "cn": "制作助理", + "jp": "製作補佐 アソシエイトプロデューサー", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "25": { + "en": "Background Art", + "cn": "背景美术", + "jp": "背景", + "categories": [ + { + "order": 6, + "en": "art", + "cn": "美术类" + }, + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "26": { + "en": "Color Setting", + "cn": "色彩指定", + "jp": "", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + }, + { + "order": 10, + "en": "colorist", + "cn": "色彩类" + } + ] + }, + "27": { + "en": "Digital Paint", + "cn": "数码绘图", + "jp": "", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "28": { + "en": "Editing", + "cn": "剪辑", + "jp": "編集", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "29": { + "en": "Original Plan", + "cn": "原案", + "jp": "", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + } + ] + }, + "30": { + "en": "Theme Song Arrangement", + "cn": "主题歌编曲", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "31": { + "en": "Theme Song Composition", + "cn": "主题歌作曲", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "32": { + "en": "Theme Song Lyrics", + "cn": "主题歌作词", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "33": { + "en": "Theme Song Performance", + "cn": "主题歌演出", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "34": { + "en": "Inserted Song Performance", + "cn": "插入歌演出", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "35": { + "en": "Planning", + "cn": "企画", + "jp": "プランニング 企画開発", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "36": { + "en": "Planning Producer", + "cn": "企划制作人", + "jp": "企画プロデューサー 企画営業プロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + } + ] + }, + "37": { + "en": "Production Manager", + "cn": "制作管理", + "jp": "制作マネージャー 制作担当 制作班長", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "38": { + "en": "Publicity", + "cn": "宣传", + "jp": "パブリシティ 宣伝 広告宣伝 番組宣伝 製作宣伝", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "39": { + "en": "Recording", + "cn": "录音", + "jp": "録音", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "40": { + "en": "Recording Assistant", + "cn": "录音助理", + "jp": "録音アシスタント 録音助手", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "41": { + "en": "Series Production Director", + "cn": "系列监督", + "jp": "", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "42": { + "en": "Production", + "cn": "製作", + "jp": "", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "43": { + "en": "Setting", + "cn": "设定", + "jp": "設定", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "44": { + "en": "Sound Director", + "cn": "音响监督", + "jp": "", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "45": { + "en": "Sound", + "cn": "音响", + "jp": "音響 音声", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "46": { + "en": "Sound Effects", + "cn": "音效", + "jp": "音響効果", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "47": { + "en": "Special Effects", + "cn": "特效", + "jp": "特殊効果", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + }, + { + "order": 11, + "en": "visual", + "cn": "视觉类" + } + ] + }, + "48": { + "en": "ADR Director", + "cn": "配音监督", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + }, + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "49": { + "en": "Co-Director", + "cn": "联合导演", + "jp": "", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "50": { + "en": "Setting", + "cn": "背景设定", + "jp": "基本設定 場面設定 場面設計 設定", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "51": { + "en": "In-Between Animation", + "cn": "补间动画", + "jp": "動画", + "categories": [ + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "52": { + "en": "Executive Producer", + "cn": "执行制片人", + "jp": "製作総指揮", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + } + ] + }, + "53": { + "en": "Assistant Producer", + "cn": "助理制片人", + "jp": "協力プロデューサー アシスタントプロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "54": { + "en": "Producer", + "cn": "制片人", + "jp": "プロデュース プロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + } + ] + }, + "55": { + "en": "Music Assistant", + "cn": "音乐助理", + "jp": "音楽アシスタント", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "56": { + "en": "Assistant Production Manager", + "cn": "制作进行", + "jp": "制作進行", + "desc": "管理动画的制作时程、协调各部门作业、回收作画原稿等", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "57": { + "en": "Casting Director", + "cn": "演员监督", + "jp": "キャスティングコーディネーター監督", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "58": { + "en": "Chief Producer", + "cn": "总制片人", + "jp": "チーフプロデューサー チーフ制作 総合プロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + } + ] + }, + "59": { + "en": "Co-Producer", + "cn": "联合制片人", + "jp": "", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + } + ] + }, + "60": { + "en": "Dialogue Editing", + "cn": "台词编辑", + "jp": "台詞編集", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "61": { + "en": "Post-Production Assistant", + "cn": "后期制片协调", + "jp": "ポストプロダクション協力", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "62": { + "en": "Production Assistant", + "cn": "制作助理", + "jp": "制作アシスタント 制作補佐 製作補", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "63": { + "en": "Production", + "cn": "制作", + "jp": "製作 製作スタジオ", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "64": { + "en": "Production Coordination", + "cn": "制作协调", + "jp": "制作コーディネーター", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "65": { + "en": "Music Work", + "cn": "音乐制作", + "jp": "楽曲制作 音楽制作", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "66": { + "en": "Special Thanks", + "cn": "特别鸣谢", + "jp": "友情協力", + "categories": [ + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "67": { + "en": "Animation Work", + "cn": "动画制作", + "jp": "アニメーション制作 アニメ制作 アニメーション", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "69": { + "en": "CG Director", + "cn": "CG 导演", + "jp": "CG 監督", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "70": { + "en": "Mechanical Animation Direction", + "cn": "机械作画监督", + "jp": "メカニック作監", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "71": { + "en": "Art Design", + "cn": "美术设计", + "jp": "美術設定", + "categories": [ + { + "order": 6, + "en": "art", + "cn": "美术类" + }, + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "72": { + "en": "Assistant Director", + "cn": "副导演", + "jp": "助監督 / 監督補佐", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "73": { + "en": "OP ED", + "cn": "OP・ED 分镜", + "jp": "OP・ED 分鏡", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + } + ] + }, + "74": { + "en": "Chief Director", + "cn": "总导演", + "jp": "総監督 / チーフディレクター", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "75": { + "en": "3DCG", + "cn": "3DCG", + "jp": "", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "76": { + "en": "Work Assistance", + "cn": "制作协力", + "jp": "制作協力 / 作品協力", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "77": { + "en": "Action Animation Direction", + "cn": "动作作画监督", + "jp": "アクション作画監督", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "80": { + "en": "Supervising Producer", + "cn": "监制", + "jp": "プロデュース", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + } + ] + }, + "81": { + "en": "Assistance", + "cn": "协力", + "jp": "協力", + "categories": [ + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "82": { + "en": "Photography", + "cn": "摄影", + "jp": "撮影", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "83": { + "en": "Assistant Production Manager Assistance", + "cn": "制作进行协力", + "jp": "制作進行協力", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "84": { + "en": "Design Manager", + "cn": "设定制作", + "jp": "設定制作 制作設定", + "desc": "有时需要额外的设计工作,联系负责部门并监督工作确保交付", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "85": { + "en": "Music Producer", + "cn": "音乐制作人", + "jp": "音楽プロデューサー", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "86": { + "en": "3DCG Director", + "cn": "3DCG 导演", + "jp": "3DCG 監督", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "87": { + "en": "Animation Producer", + "cn": "动画制片人", + "jp": "アニメプロデューサー アニメーションプロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + } + ] + }, + "88": { + "en": "Special Effects Animation Direction", + "cn": "特效作画监督", + "jp": "エフェクト作画監督", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + }, + { + "order": 11, + "en": "visual", + "cn": "视觉类" + } + ] + }, + "89": { + "en": "Chief Episode Direction", + "cn": "主演出", + "jp": "チーフ演出", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + } + ] + }, + "90": { + "en": "Assistant Animation Direction", + "cn": "作画监督助理", + "jp": "作画監督補佐", + "categories": [ + { + "order": 12, + "en": "assistant", + "cn": "助理类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "91": { + "en": "Assistant Episode Direction", + "cn": "演出助理", + "jp": "演出助手 演出補佐 演出協力", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "92": { + "en": "Main Animator", + "cn": "主动画师", + "jp": "メインアニメーター", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "93": { + "en": "", + "cn": "上色", + "jp": "仕上", + "categories": [ + { + "order": 10, + "en": "colorist", + "cn": "色彩类" + } + ] + }, + "94": { + "en": "", + "cn": "上色检查", + "jp": "仕上検查", + "categories": [ + { + "order": 10, + "en": "colorist", + "cn": "色彩类" + } + ] + }, + "95": { + "en": "", + "cn": "色彩检查", + "jp": "色検查", + "categories": [ + { + "order": 10, + "en": "colorist", + "cn": "色彩类" + } + ] + }, + "96": { + "en": "", + "cn": "美术板", + "jp": "美術ボード", + "categories": [ + { + "order": 6, + "en": "art", + "cn": "美术类" + } + ] + }, + "97": { + "en": "", + "cn": "美术", + "jp": "美術", + "categories": [ + { + "order": 6, + "en": "art", + "cn": "美术类" + } + ] + }, + "98": { + "en": "", + "cn": "印象板", + "jp": "イメージボード", + "categories": [ + { + "order": 6, + "en": "art", + "cn": "美术类" + }, + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "99": { + "en": "2D WORKS", + "cn": "2D 设计", + "jp": "2D ワークス", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "100": { + "en": "3D WORKS", + "cn": "3D 设计", + "jp": "3D ワークス", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "101": { + "en": "Technical Director", + "cn": "技术导演", + "jp": "テクニカルディレクター", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "102": { + "en": "", + "cn": "特技导演", + "jp": "特撮監督", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "103": { + "en": "Color Script", + "cn": "色彩脚本", + "jp": "カラースクリプト", + "categories": [ + { + "order": 10, + "en": "colorist", + "cn": "色彩类" + } + ] + }, + "104": { + "en": "", + "cn": "分镜协力", + "jp": "絵コンテ協力 コンテ協力", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "105": { + "en": "", + "cn": "分镜抄写", + "jp": "絵コンテ清書 コンテ清書", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "106": { + "en": "Sub-Character Design", + "cn": "副人物设定", + "jp": "サブキャラクターデザイン", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "107": { + "en": "Guest character design", + "cn": "客座人物设定", + "jp": "ゲストキャラクターデザイン", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "108": { + "en": "", + "cn": "构图监修", + "jp": "レイアウト監修", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + } + ] + }, + "109": { + "en": "", + "cn": "构图作画监督", + "jp": "レイアウト作画監督 レイアウト作監", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "110": { + "en": "", + "cn": "总作画监督助理", + "jp": "総作画監督補佐 総作監補佐", + "categories": [ + { + "order": 12, + "en": "assistant", + "cn": "助理类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "111": { + "en": "", + "cn": "道具作画监督", + "jp": "プロップ作画監督 プロップ作監", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "112": { + "en": "Concept Design", + "cn": "概念设计", + "jp": "コンセプトデザイン", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "113": { + "en": "Costume Design", + "cn": "服装设计", + "jp": "衣装デザイン 衣装設定", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "114": { + "en": "Title Design", + "cn": "标题设计", + "jp": "タイトルデザイン", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "115": { + "en": "Setting Cooperation", + "cn": "设定协力", + "jp": "設定協力 デザイン協力", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "116": { + "en": "Music Director", + "cn": "音乐监督", + "jp": "音楽ディレクター", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + }, + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "117": { + "en": "Music Selection", + "cn": "选曲", + "jp": "選曲", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "118": { + "en": "Inserted Song Lyrics", + "cn": "插入歌作词", + "jp": "Insert Song Lyrics", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "119": { + "en": "Inserted Song Composition", + "cn": "插入歌作曲", + "jp": "Insert Song Composition", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "120": { + "en": "Inserted Song Arrangement", + "cn": "插入歌编曲", + "jp": "Insert Song Arrangement", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "121": { + "en": "Creative Producer", + "cn": "创意制片人", + "jp": "クリエイティブプロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + } + ] + }, + "122": { + "en": "Associate Producer", + "cn": "副制片人", + "jp": "アソシエイトプロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "123": { + "en": "Chief Production Supervisor", + "cn": "制作统括", + "jp": "制作統括", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "124": { + "en": "Line Producer", + "cn": "现场制片人", + "jp": "ラインプロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "125": { + "en": "Literary Producer", + "cn": "文艺制作", + "jp": "文芸制作", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + } + ] + }, + "127": { + "en": "Planning Cooperation", + "cn": "企画协力", + "jp": "企画協力", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "128": { + "en": "OP・ED Direction", + "cn": "OP・ED 演出", + "jp": "OP・ED 演出", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + } + ] + }, + "129": { + "en": "Bank Storyboard Direction", + "cn": "Bank 分镜演出", + "jp": "バンク コンテ・演出", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + } + ] + }, + "130": { + "en": "Live Storyboard Direction", + "cn": "Live 分镜演出", + "jp": "ライブ コンテ・演出", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + } + ] + }, + "131": { + "en": "Meta-story Storyboard Direction", + "cn": "剧中剧分镜演出", + "jp": "劇中劇 コンテ・演出", + "categories": [ + { + "order": 3, + "en": "direction", + "cn": "演出类" + } + ] + }, + "132": { + "en": "Meta-story Character Design", + "cn": "剧中剧人设", + "jp": "劇中劇 キャラクターデザイン", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "133": { + "en": "Visual Director", + "cn": "视觉导演", + "jp": "ビジュアルディレクター", + "categories": [ + { + "order": 11, + "en": "visual", + "cn": "视觉类" + }, + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "134": { + "en": "Creative Supervisor/Director", + "cn": "创意总监", + "jp": "クリエイティブスーパーバイザー クリエイティブディレクター", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + } + ] + }, + "135": { + "en": "Tokusatsu Effects", + "cn": "特摄效果", + "jp": "特撮", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + }, + { + "order": 11, + "en": "visual", + "cn": "视觉类" + } + ] + }, + "136": { + "en": "Visual Effects", + "cn": "视觉效果", + "jp": "ビジュアルエフェクト", + "categories": [ + { + "order": 11, + "en": "visual", + "cn": "视觉类" + } + ] + }, + "137": { + "en": "", + "cn": "动作导演", + "jp": "アクション監督", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "138": { + "en": "Eyecatch Art", + "cn": "转场绘", + "jp": "アイキャッチ", + "categories": [ + { + "order": 4, + "en": "animation", + "cn": "作画类" + }, + { + "order": 11, + "en": "visual", + "cn": "视觉类" + } + ] + }, + "139": { + "en": "Illustration", + "cn": "插画", + "jp": "イラスト", + "categories": [ + { + "order": 4, + "en": "animation", + "cn": "作画类" + }, + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "140": { + "en": "Character Animation Director", + "cn": "角色作画监督", + "jp": "キャラクター作画監督", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "141": { + "en": "Animation Supervisor", + "cn": "作画监修", + "jp": "作画監修", + "categories": [ + { + "order": 1, + "en": "director", + "cn": "导演类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "142": { + "en": "Mechanical Design Concept", + "cn": "机设原案", + "jp": "メカニカル原案", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "143": { + "en": "Concept Art", + "cn": "概念艺术", + "jp": "コンセプトアート", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "144": { + "en": "Visual Concept", + "cn": "视觉概念", + "jp": "ビジュアルコンセプト", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + }, + { + "order": 11, + "en": "visual", + "cn": "视觉类" + } + ] + }, + "145": { + "en": "Scene Design", + "cn": "画面设计", + "jp": "画面設計", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + }, + { + "order": 11, + "en": "visual", + "cn": "视觉类" + } + ] + }, + "146": { + "en": "Monster Design", + "cn": "怪物设计", + "jp": "モンスターデザイン", + "categories": [ + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + }, + "147": { + "en": "Story Concept", + "cn": "故事概念", + "jp": "ストーリーコンセプト", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + } + ] + }, + "148": { + "en": "Scenario Coordinator", + "cn": "剧本协调", + "jp": "シナリオコーディネーター", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + } + ] + }, + "149": { + "en": "Script Cooperation", + "cn": "脚本协力", + "jp": "脚本協力", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "150": { + "en": "Associate Series Composition", + "cn": "副系列构成", + "jp": "副シリーズ構成", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + } + ] + }, + "151": { + "en": "Series Composition Cooperation", + "cn": "构成协力", + "jp": "構成協力", + "categories": [ + { + "order": 2, + "en": "storyboard", + "cn": "分镜/脚本类" + }, + { + "order": 12, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "152": { + "en": "Recording Studio", + "cn": "录音工作室", + "jp": "録音スタジオ", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "153": { + "en": "Sound Mixing", + "cn": "整音", + "jp": "整音", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "154": { + "en": "Sound Production Coordinator", + "cn": "音响制作担当", + "jp": "音響制作担当", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "155": { + "en": "Online Editing", + "cn": "在线剪辑", + "jp": "オンライン編集", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "156": { + "en": "Offline Editing", + "cn": "离线剪辑", + "jp": "オフライン編集", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "157": { + "en": "3D Animator", + "cn": "3D 动画师", + "jp": "3Dアニメーター 3Dアニメーション", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作类" + }, + { + "order": 4, + "en": "animation", + "cn": "作画类" + } + ] + }, + "158": { + "en": "CG Producer", + "cn": "CG 制作人", + "jp": "CGプロデューサー CGIプロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "159": { + "en": "Publicity Producer", + "cn": "宣传制片人", + "jp": "宣伝プロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "160": { + "en": "Art Producer", + "cn": "美术制作人", + "jp": "美術プロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 6, + "en": "art", + "cn": "美术类" + } + ] + }, + "161": { + "en": "Sound Producer", + "cn": "音响制作人", + "jp": "音響プロデューサー", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "162": { + "en": "CG Production Coordinator", + "cn": "CG 制作进行", + "jp": "CG進行", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "163": { + "en": "Art Production Coordinator", + "cn": "美术制作进行", + "jp": "美術進行", + "categories": [ + { + "order": 9, + "en": "producer", + "cn": "制片类" + }, + { + "order": 6, + "en": "art", + "cn": "美术类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "164": { + "en": "Assistant Art Director", + "cn": "美术监督助理", + "jp": "美術監督補佐", + "categories": [ + { + "order": 12, + "en": "assistant", + "cn": "助理类" + }, + { + "order": 6, + "en": "art", + "cn": "美术类" + } + ] + }, + "165": { + "en": "Assistant Color Designer", + "cn": "色彩设计助理", + "jp": "色彩設計補佐", + "categories": [ + { + "order": 12, + "en": "assistant", + "cn": "助理类" + }, + { + "order": 10, + "en": "colorist", + "cn": "色彩类" + } + ] + }, + "166": { + "en": "Assistant Director of Photography", + "cn": "摄影监督助理", + "jp": "撮影監督補佐", + "categories": [ + { + "order": 12, + "en": "assistant", + "cn": "助理类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "167": { + "en": "Assistant Production Desk", + "cn": "制作管理助理", + "jp": "制作デスク補佐", + "categories": [ + { + "order": 12, + "en": "assistant", + "cn": "助理类" + }, + { + "order": 8, + "en": "production", + "cn": "制作类" + } + ] + }, + "168": { + "en": "Assistant Design Manager", + "cn": "设定制作助理", + "jp": "設定制作補佐", + "categories": [ + { + "order": 12, + "en": "assistant", + "cn": "助理类" + }, + { + "order": 5, + "en": "design", + "cn": "设定类" + } + ] + } + }, + "3": { + "3001": { + "en": "Artist", + "cn": "艺术家", + "jp": "" + }, + "3002": { + "en": "Producer", + "cn": "制作人", + "jp": "" + }, + "3003": { + "en": "Composer", + "cn": "作曲", + "jp": "" + }, + "3004": { + "en": "Label", + "cn": "厂牌", + "jp": "レーベル" + }, + "3005": { + "en": "Original Creator/Original Work", + "cn": "原作", + "jp": "" + }, + "3006": { + "en": "Lyric", + "cn": "作词", + "jp": "" + }, + "3007": { + "en": "Recording", + "cn": "录音", + "jp": "" + }, + "3008": { + "en": "Arrange", + "cn": "编曲", + "jp": "" + }, + "3009": { + "en": "Illustrator", + "cn": "插图", + "jp": "" + }, + "3010": { + "en": "Scenario", + "cn": "脚本", + "jp": "シナリオ" + }, + "3011": { + "en": "O.P.", + "cn": "出版方", + "jp": "音楽出版社" + }, + "3012": { + "en": "Mastering", + "cn": "母带制作", + "jp": "" + }, + "3013": { + "en": "Mixing", + "cn": "混音", + "jp": "" + }, + "3014": { + "en": "Instrument", + "cn": "乐器", + "jp": "" + }, + "3015": { + "en": "Vocal", + "cn": "声乐", + "jp": "" + } + }, + "4": { + "1001": { + "en": "Developer", + "cn": "开发", + "jp": "開発元", + "categories": [ + { + "order": 1, + "en": "producer", + "cn": "发行类" + } + ] + }, + "1002": { + "en": "Publisher", + "cn": "发行", + "jp": "発売元", + "categories": [ + { + "order": 1, + "en": "producer", + "cn": "发行类" + } + ] + }, + "1003": { + "en": "Game Designer", + "cn": "游戏设计师", + "jp": "ゲームクリエイター", + "categories": [ + { + "order": 2, + "en": "director", + "cn": "导演类" + } + ] + }, + "1004": { + "en": "", + "cn": "剧本", + "jp": "腳本", + "categories": [ + { + "order": 3, + "en": "storyboard", + "cn": "脚本类" + } + ] + }, + "1005": { + "en": "", + "cn": "美工", + "jp": "美術", + "categories": [ + { + "order": 5, + "en": "art", + "cn": "美术类" + } + ] + }, + "1006": { + "en": "", + "cn": "音乐", + "jp": "音楽", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "1007": { + "en": "", + "cn": "关卡设计", + "jp": "", + "categories": [ + { + "order": 4, + "en": "design", + "cn": "设定类" + } + ] + }, + "1008": { + "en": "Character Design", + "cn": "人物设定", + "jp": "キャラ設定 キャラクターデザイン", + "categories": [ + { + "order": 4, + "en": "design", + "cn": "设定类" + } + ] + }, + "1009": { + "en": "Theme Song Composition", + "cn": "主题歌作曲", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "1010": { + "en": "Theme Song Lyrics", + "cn": "主题歌作词", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "1011": { + "en": "Theme Song Performance", + "cn": "主题歌演出", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "1012": { + "en": "Inserted Song Performance", + "cn": "插入歌演出", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "1013": { + "en": "", + "cn": "原画", + "jp": "", + "categories": [ + { + "order": 5, + "en": "art", + "cn": "美术类" + } + ] + }, + "1014": { + "en": "Animation Work", + "cn": "动画制作", + "jp": "アニメーション制作 アニメ制作 アニメーション", + "categories": [ + { + "order": 6, + "en": "animation", + "cn": "动画类" + } + ] + }, + "1015": { + "en": "", + "cn": "原作", + "jp": "", + "categories": [ + { + "order": 4, + "en": "design", + "cn": "设定类" + } + ] + }, + "1016": { + "en": "Director/Direction", + "cn": "导演", + "jp": "監督 演出 シリーズ監督", + "categories": [ + { + "order": 2, + "en": "director", + "cn": "导演类" + } + ] + }, + "1017": { + "en": "", + "cn": "动画监督", + "jp": "アニメーション監督", + "categories": [ + { + "order": 6, + "en": "animation", + "cn": "动画类" + } + ] + }, + "1018": { + "en": "", + "cn": "制作总指挥", + "jp": "", + "categories": [ + { + "order": 2, + "en": "director", + "cn": "导演类" + } + ] + }, + "1019": { + "en": "QC", + "cn": "QC", + "jp": "", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作/程序类" + } + ] + }, + "1020": { + "en": "", + "cn": "动画剧本", + "jp": "アニメーション脚本", + "categories": [ + { + "order": 6, + "en": "animation", + "cn": "动画类" + } + ] + }, + "1021": { + "en": "Program", + "cn": "程序", + "jp": "プログラム", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作/程序类" + } + ] + }, + "1022": { + "en": "", + "cn": "协力", + "jp": "協力", + "categories": [ + { + "order": 0, + "en": "assistant", + "cn": "助理类" + } + ] + }, + "1023": { + "en": "", + "cn": "CG 监修", + "jp": "CG 監修", + "categories": [ + { + "order": 5, + "en": "art", + "cn": "美术类" + } + ] + }, + "1024": { + "en": "", + "cn": "SD原画", + "jp": "", + "categories": [ + { + "order": 5, + "en": "art", + "cn": "美术类" + } + ] + }, + "1025": { + "en": "", + "cn": "背景", + "jp": "", + "categories": [ + { + "order": 5, + "en": "art", + "cn": "美术类" + } + ] + }, + "1026": { + "en": "", + "cn": "监修", + "jp": "監修", + "categories": [ + { + "order": 2, + "en": "director", + "cn": "导演类" + } + ] + }, + "1027": { + "en": "", + "cn": "系列构成", + "jp": "シリーズ構成", + "categories": [ + { + "order": 3, + "en": "storyboard", + "cn": "脚本类" + } + ] + }, + "1028": { + "en": "", + "cn": "企画", + "jp": "", + "categories": [ + { + "order": 8, + "en": "production", + "cn": "制作/程序类" + } + ] + }, + "1029": { + "en": "Mechanical Design", + "cn": "机械设定", + "jp": "メカニック設定", + "categories": [ + { + "order": 4, + "en": "design", + "cn": "设定类" + } + ] + }, + "1030": { + "en": "Sound Director", + "cn": "音响监督", + "jp": "", + "categories": [ + { + "order": 7, + "en": "music", + "cn": "声音类" + } + ] + }, + "1031": { + "en": "", + "cn": "作画监督", + "jp": "作画監督", + "categories": [ + { + "order": 5, + "en": "art", + "cn": "美术类" + } + ] + }, + "1032": { + "en": "Producer", + "cn": "制作人", + "jp": "プロデューサー", + "categories": [ + { + "order": 2, + "en": "director", + "cn": "导演类" + } + ] + }, + "1033": { + "en": "Cover Art", + "cn": "海报", + "jp": "表紙", + "categories": [ + { + "order": 5, + "en": "art", + "cn": "美术类" + } + ] + } + }, + "6": { + "4001": { + "en": "Creator", + "cn": "原作", + "jp": "" + }, + "4002": { + "en": "Director", + "cn": "导演", + "jp": "" + }, + "4003": { + "en": "Writer", + "cn": "编剧", + "jp": "" + }, + "4004": { + "en": "Composer", + "cn": "音乐", + "jp": "" + }, + "4005": { + "en": "Executive Producer", + "cn": "执行制片人", + "jp": "製作総指揮" + }, + "4006": { + "en": "Co-Executive Producer", + "cn": "共同执行制作", + "jp": "" + }, + "4007": { + "en": "Producer", + "cn": "制片人/制作人", + "jp": "プロデューサー" + }, + "4008": { + "en": "Supervising Producer", + "cn": "监制", + "jp": "" + }, + "4009": { + "en": "Consulting Producer", + "cn": "副制作人/制作顾问", + "jp": "" + }, + "4010": { + "en": "Story", + "cn": "故事", + "jp": "" + }, + "4011": { + "en": "Story Editor", + "cn": "编审", + "jp": "" + }, + "4012": { + "en": "Editor", + "cn": "剪辑", + "jp": "" + }, + "4013": { + "en": "Creative Director", + "cn": "创意总监", + "jp": "" + }, + "4014": { + "en": "Cinematography", + "cn": "摄影", + "jp": "" + }, + "4015": { + "en": "Theme Song Performance", + "cn": "主题歌演出", + "jp": "" + }, + "4016": { + "en": "Actor", + "cn": "主演", + "jp": "" + }, + "4017": { + "en": "Supporting Actor", + "cn": "配角", + "jp": "" + }, + "4018": { + "en": "Production", + "cn": "制作", + "jp": "製作 製作スタジオ" + }, + "4019": { + "en": "Present", + "cn": "出品", + "jp": "配給" + }, + "4020": { + "en": "ADR Director", + "cn": "配音导演", + "jp": "" + }, + "4021": { + "en": "Recording", + "cn": "录音", + "jp": "録音" + }, + "4022": { + "en": "Poster", + "cn": "海报", + "jp": "" + } + } + } +} diff --git a/pkg/wiki/bench_test.go b/pkg/wiki/bench_test.go deleted file mode 100644 index 98e6bd144..000000000 --- a/pkg/wiki/bench_test.go +++ /dev/null @@ -1,293 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package wiki_test - -import ( - "runtime" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/bangumi/server/pkg/wiki" -) - -const large = ` {{Infobox animanga/TVAnime -|中文名= 潜行吧!奈亚子W -|别名={ -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] -} -|话数= 12 -|放送开始= 2013年4月7日 -|放送星期= -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|别名={ -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] -} -| 播放结束 = 2013年6月30日 -| 播放结束 = 2013年6月30日 -| 播放结束= 2013年6月30日 - -|播放结束= 2013年6月30日 -|别名={ - - -[2013年6月30日] - - -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] - -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] -} -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 - - - - - - - - - - - - - -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|别名={ -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] -[ 2013年6月30日] -} -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|其他= -|Copyright= -|原作= 逢空万太 -|导演= 長澤剛 -|人物设定= 滝山真哲 -}} ` + "\t" - -const medium = ` {{Infobox animanga/TVAnime -|中文名= 潜行吧!奈亚子W -|别名={ -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] -} -|话数= 12 -|放送开始= 2013年4月7日 -|放送星期= -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|别名={ -[1|2013年6月30日] -[2|2013年6月30日] -[3|2013年6月30日] -[4|2013年6月30日] -[4|2013年6月30日] -[4|2013年6月30日] -[4|2013年6月30日] -} -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|其他= -|Copyright= -|原作= 逢空万太 -|导演= 長澤剛 -|人物设定= 滝山真哲 -}} ` + "\t" - -const small = ` {{Infobox animanga/TVAnime -|中文名= 潜行吧!奈亚子W -|别名={ -[2013年6月30日] -} -|话数= 12 -|别名={ -[1|2013年6月30日] -} -|播放结束= 2013年6月30日 -|其他= -|Copyright= -}} ` + "\t" - -func BenchmarkParse_large(b *testing.B) { - var w wiki.Wiki - for i := 0; i < b.N; i++ { - w, _ = wiki.Parse(large) - } - runtime.KeepAlive(w) -} - -func BenchmarkParse_medium(b *testing.B) { - var w wiki.Wiki - for i := 0; i < b.N; i++ { - w, _ = wiki.Parse(medium) - } - runtime.KeepAlive(w) -} - -func BenchmarkParse_small(b *testing.B) { - var w wiki.Wiki - for i := 0; i < b.N; i++ { - w, _ = wiki.Parse(small) - } - runtime.KeepAlive(w) -} - -func BenchmarkWiki_NonZero(b *testing.B) { - var w, err = wiki.Parse(benchNonZeroInput) - require.NoError(b, err) - - var r wiki.Wiki - for i := 0; i < b.N; i++ { - r = w.NonZero() - } - runtime.KeepAlive(r) -} - -const benchNonZeroInput = ` {{Infobox animanga/TVAnime -|中文名= 潜行吧!奈亚子W -|别名={ -} -|话数= 12 -|放送开始= 2013年4月7日 -|放送星期= -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= -|播放结束= -|播放结束= -|播放结束= 2013年6月30日 -|别名={ -[] -[2013年6月30日] -[2013年6月30日] -[] -[2013年6月30日] -[2013年6月30日] -[] -[2013年6月30日] -[2013年6月30日] -} -| 播放结束 = 2013年6月30日 -| 播放结束 = 2013年6月30日 -| 播放结束= 2013年6月30日 -| 播放结束= 2013年6月30日 -| 播放结束= - -|播放结束= 2013年6月30日 -|别名={ - - -[2013年6月30日] - - -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] - -[2013年6月30日] -[2013年6月30日] -[] -[2013年6月30日] -[] -} -|播放结束= 2013年6月30日 -| 播放结束= -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 - - - - - - - - - - - - - -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -| 播放结束= -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|别名={ -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] -[2013年6月30日] -[ 2013年6月30日] -} -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|播放结束= 2013年6月30日 -|其他= -|Copyright= -|原作= 逢空万太 -|导演= 長澤剛 -|人物设定= 滝山真哲 -}} ` + "\t" diff --git a/pkg/wiki/error.go b/pkg/wiki/error.go deleted file mode 100644 index ad645b244..000000000 --- a/pkg/wiki/error.go +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package wiki - -import "strconv" - -var _ interface { - Error() string - Unwrap() error -} = (*SyntaxError)(nil) - -type SyntaxError struct { - Err error - Line string - Lino int -} - -func (p *SyntaxError) Error() string { - return p.Err.Error() + " line: " + strconv.Itoa(p.Lino) + " " + strconv.Quote(p.Line) -} - -func (p *SyntaxError) Unwrap() error { - return p.Err -} - -func wrapError(err error, lino int, line string) error { - return &SyntaxError{ - Line: line, - Lino: lino, - Err: err, - } -} diff --git a/pkg/wiki/parser.go b/pkg/wiki/parser.go deleted file mode 100644 index d65f4e20c..000000000 --- a/pkg/wiki/parser.go +++ /dev/null @@ -1,210 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package wiki - -import ( - "errors" - "fmt" - "strings" - - "github.com/bangumi/server/internal/pkg/generic/slice" -) - -var ( - ErrWikiSyntax = errors.New("invalid wiki syntax") - ErrGlobalPrefix = fmt.Errorf("%w: missing prefix '{{Infobox' at the start", ErrWikiSyntax) - ErrGlobalSuffix = fmt.Errorf("%w: missing '}}' at the end", ErrWikiSyntax) - ErrArrayNoClose = fmt.Errorf("%w: array should be closed by '}'", ErrWikiSyntax) - ErrArrayItemWrapped = fmt.Errorf("%w: array item should be wrapped by '[]'", ErrWikiSyntax) - ErrExpectingNewField = fmt.Errorf("%w: missing '|' to start a new field", ErrWikiSyntax) - ErrExpectingSignEqual = fmt.Errorf("%w: missing '=' to separate field name and value", ErrWikiSyntax) -) - -// ParseOmitError try to parse a string as wiki, omitting error. -func ParseOmitError(s string) Wiki { - w, err := Parse(s) - if err != nil { - return Wiki{} - } - - return w -} - -const prefix = "{{Infobox" -const suffix = "}}" - -//nolint:funlen,gocognit,gocyclo -func Parse(s string) (Wiki, error) { - var w = Wiki{} - s, lineOffset := processInput(s) - if s == "" { - return w, nil - } - - if !strings.HasPrefix(s, prefix) { - return Wiki{}, ErrGlobalPrefix - } - - eolCount := strings.Count(s, "\n") - - if !strings.HasSuffix(s, suffix) { - return Wiki{}, ErrGlobalSuffix - } - - w.Type = readType(s) - w.Fields = make([]Field, 0) // make zero value in json '[]', no alloc with cap 0 - - if eolCount <= 1 { - return w, nil - } - - w.Fields = make([]Field, 0, eolCount-1) - // pre-alloc for all items. - var itemContainer = make([]Item, 0, eolCount-2) - - // loop state - var inArray = false - var currentField Field - - // variable to loop line - var firstEOL = strings.IndexByte(s, '\n') // skip first line - var secondLastEOL = 0 - var lastEOL = firstEOL + 1 - var lino = lineOffset - 1 // current line number - var offset int - var line string - for { - // fast iter lines without alloc - offset = strings.IndexByte(s[lastEOL:], '\n') - if offset != -1 { - line = s[lastEOL : lastEOL+offset] - secondLastEOL = lastEOL - lastEOL = lastEOL + offset + 1 - lino++ - } else { - // can't find next line - if inArray { - // array should be close have read all contents - return Wiki{}, wrapError(ErrArrayNoClose, lino+1, s[secondLastEOL:lastEOL]) - } - - break - } - - // now handle line content - line = trimSpace(line) - if line == "" { - continue - } - - if line[0] == '|' { - // new field - currentField = Field{} - if inArray { - return Wiki{}, wrapError(ErrArrayNoClose, lino, line) - } - - key, value, err := readStartLine(trimLeftSpace(line[1:])) // read "key = value" - if err != nil { - return Wiki{}, wrapError(err, lino, line) - } - - switch value { - case "": - w.Fields = append(w.Fields, Field{Key: key, Null: true}) - - continue - case "{": - inArray = true - currentField.Key = key - currentField.Array = true - - continue - } - - w.Fields = append(w.Fields, Field{Key: key, Value: value}) - - continue - } - - if inArray { - if line == "}" { // close array - inArray = false - currentField.Values = slice.Clone(itemContainer) - itemContainer = itemContainer[:0] - w.Fields = append(w.Fields, currentField) - - continue - } - // array item - key, value, err := readArrayItem(line) - if err != nil { - return Wiki{}, wrapError(err, lino, line) - } - itemContainer = append(itemContainer, Item{ - Key: key, - Value: value, - }) - } - - if !inArray { - return Wiki{}, wrapError(ErrExpectingNewField, lino, line) - } - } - - return w, nil -} - -func readType(s string) string { - i := strings.IndexByte(s, '\n') - if i == -1 { - i = strings.IndexByte(s, '}') // {{Infobox Crt}} - } - - return trimSpace(s[len(prefix):i]) -} - -// read whole line as an array item, spaces are trimmed. -// -// readArrayItem("[简体中文名|鲁鲁修]") => "简体中文名", "鲁鲁修", nil -// readArrayItem("[简体中文名|]") => "简体中文名", "", nil -// readArrayItem("[鲁鲁修]") => "", "鲁鲁修", nil -func readArrayItem(line string) (string, string, error) { - if line[0] != '[' || line[len(line)-1] != ']' { - return "", "", ErrArrayItemWrapped - } - - content := line[1 : len(line)-1] - - before, after, found := strings.Cut(content, "|") - if !found { - return "", trimSpace(content), nil - } - - return trimSpace(before), trimSpace(after), nil -} - -// read line without leading '|' as key value pair, spaces are trimmed. -// -// readStartLine("播放日期 = 2017年4月16日") => 播放日期, 2017年4月16日, nil -// readStartLine("播放日期 = ") => 播放日期, "", nil -func readStartLine(line string) (string, string, error) { - before, after, found := strings.Cut(line, "=") - if !found { - return "", "", ErrExpectingSignEqual - } - - return trimRightSpace(before), trimLeftSpace(after), nil -} diff --git a/pkg/wiki/parser_test.go b/pkg/wiki/parser_test.go deleted file mode 100644 index d36035244..000000000 --- a/pkg/wiki/parser_test.go +++ /dev/null @@ -1,195 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package wiki_test - -import ( - "regexp" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/bangumi/server/pkg/wiki" -) - -func TestParseFull(t *testing.T) { - t.Parallel() - value, err := wiki.Parse(`{{Infobox Crt -|简体中文名= 水树奈奈 -|官网= https://www.mizukinana.jp -|FanClub= https://fanclub.mizukinana.jp -|Twitter= https://twitter.com/NM_NANAPARTY -}}`) - - require.NoError(t, err) - - require.Equal(t, wiki.Wiki{ - Type: "Crt", - Fields: []wiki.Field{ - {Key: "简体中文名", Value: "水树奈奈"}, - {Key: "官网", Value: "https://www.mizukinana.jp"}, - {Key: "FanClub", Value: "https://fanclub.mizukinana.jp"}, - {Key: "Twitter", Value: "https://twitter.com/NM_NANAPARTY"}, - }}, value) -} - -var expected = wiki.Wiki{ - Type: "Crt", - Fields: []wiki.Field{ - {Key: "简体中文名", Value: "水树奈奈"}, - {Key: "别名", Array: true, Values: []wiki.Item{ - {Key: "", Value: "第二中文名"}, - {Key: "", Value: "英文名"}, - {Key: "日文名", Value: "近藤奈々 (こんどう なな)"}, - {Key: "纯假名", Value: "みずき なな"}, - {Key: "罗马字", Value: "Mizuki Nana"}, - {Key: "昵称", Value: "奈々ちゃん、奈々さん、奈々様、お奈々、ヘッド"}, - {Key: "其他名义", Value: ""}, - }}, - }, -} - -func TestParseFullArray(t *testing.T) { - t.Parallel() - value, err := wiki.Parse(`{{Infobox Crt -|简体中文名= 水树奈奈 -|别名={ -[第二中文名] -[英文名] -[日文名|近藤奈々 (こんどう なな)] -[纯假名|みずき なな] -[罗马字|Mizuki Nana] -[昵称|奈々ちゃん、奈々さん、奈々様、お奈々、ヘッド] -[其他名义|] -} -}}`) - - require.NoError(t, err) - - require.Equal(t, expected, value) -} - -func TestParseEmptyLine(t *testing.T) { - t.Parallel() - value, err := wiki.Parse(`{{Infobox Crt -|简体中文名= 水树奈奈 -|别名={ - - -[第二中文名] -[英文名] -[日文名|近藤奈々 (こんどう なな)] - -[纯假名|みずき なな] -[罗马字|Mizuki Nana] -[昵称|奈々ちゃん、奈々さん、奈々様、お奈々、ヘッド] -[其他名义|] - -} -}}`) - - require.NoError(t, err) - require.Equal(t, expected, value) -} - -func TestParseExtraSpace(t *testing.T) { - t.Parallel() - value, err := wiki.Parse(`{{Infobox Crt -|简体中文名= 水树奈奈 -| 别名 = { -[第二中文名] -[ 英文名] -[日文名|近藤奈々 (こんどう なな)] -[纯假名 |みずき なな] -[罗马字|Mizuki Nana] -[昵称|奈々ちゃん、奈々さん、奈々様、お奈々、ヘッド] -[其他名义|] - - } -}}`) - - require.NoError(t, err) - require.Equal(t, expected, value) -} - -func TestArrayNoClose(t *testing.T) { - t.Parallel() - _, err := wiki.Parse(`{{Infobox Crt -| 别名 = { - -[昵称|奈々ちゃん、奈々さん、奈々様、お奈々、ヘッド] -[其他名义|] -}}`) - - require.ErrorIs(t, err, wiki.ErrWikiSyntax) - require.Regexp(t, regexp.MustCompile("array.*close"), err) -} - -func TestArrayNoClose2(t *testing.T) { - t.Parallel() - _, err := wiki.Parse(`{{Infobox Crt -| 别名 = { - -[昵称|奈々ちゃん、奈々さん、奈々様、お奈々、ヘッド] -[其他名义|] -|简体中文名= 水树奈奈 -}}`) - - require.ErrorIs(t, err, wiki.ErrWikiSyntax) - require.Regexp(t, regexp.MustCompile("array.*closed"), err) - require.Regexp(t, regexp.MustCompile("line: 6"), err) -} - -func TestArrayNoClose_empty_item(t *testing.T) { - t.Parallel() - _, err := wiki.Parse(`{{Infobox Crt -| 别名 = { -}}`) - - require.ErrorIs(t, err, wiki.ErrWikiSyntax) - require.Regexp(t, regexp.MustCompile("array.*closed"), err) - require.Regexp(t, regexp.MustCompile("line: 3"), err) -} - -func TestScalar_No_sign_equal(t *testing.T) { - t.Parallel() - _, err := wiki.Parse(`{{Infobox Crt -| 别名 -}}`) - - require.ErrorIs(t, err, wiki.ErrExpectingSignEqual) - require.Regexp(t, regexp.MustCompile("别名"), err) - require.Regexp(t, regexp.MustCompile("line: 2"), err) -} - -func TestTypeNoLineBreak(t *testing.T) { - t.Parallel() - w, err := wiki.Parse(`{{Infobox Crt}}`) - require.NoError(t, err) - require.Equal(t, "Crt", w.Type) -} - -func TestErrorMissingPrefix(t *testing.T) { - t.Parallel() - - _, err := wiki.Parse("\n\nNotPrefix Crt\n}}") - require.ErrorIs(t, err, wiki.ErrGlobalPrefix) -} - -func TestErrorMissingSuffix(t *testing.T) { - t.Parallel() - - _, err := wiki.Parse("\n\n{{Infobox Crt\n\n\n") - require.ErrorIs(t, err, wiki.ErrGlobalSuffix) -} diff --git a/pkg/wiki/spec_test.go b/pkg/wiki/spec_test.go deleted file mode 100644 index e3c9c0f0b..000000000 --- a/pkg/wiki/spec_test.go +++ /dev/null @@ -1,143 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package wiki_test - -import ( - "os" - "path/filepath" - "strings" - "testing" - - "github.com/stretchr/testify/require" - "gopkg.in/yaml.v3" - - "github.com/bangumi/server/pkg/wiki" -) - -const testRoot = "./testdata/wiki-syntax-spec/tests/" - -type Result struct { - Type string `yaml:"type"` - Data []Field `yaml:"data"` -} - -func (r Result) Wiki() wiki.Wiki { - fields := make([]wiki.Field, len(r.Data)) - for i, datum := range r.Data { - fields[i] = datum.Wiki() - } - return wiki.Wiki{Type: r.Type, Fields: fields} -} - -type Field struct { - Key string `yaml:"key"` - Value string `yaml:"value"` - Values []Item `yaml:"values"` - Array bool `yaml:"array"` -} - -func (i Field) Wiki() wiki.Field { - var values []wiki.Item - if i.Array { - values = make([]wiki.Item, len(i.Values)) - for i, datum := range i.Values { - values[i] = datum.Wiki() - } - } - return wiki.Field{ - Key: i.Key, - Value: i.Value, - Values: values, - Array: i.Array, - Null: len(i.Values) == 0 && i.Value == "", - } -} - -type Item struct { - K string `yaml:"k"` - V string `yaml:"v"` -} - -func (i Item) Wiki() wiki.Item { - return wiki.Item{ - Key: i.K, - Value: i.V, - } -} - -func TestAgainstInvalidSpec(t *testing.T) { - t.Parallel() - checkSubmodule(t) - var caseRoot = filepath.Join(testRoot, "invalid") - files, err := os.ReadDir(caseRoot) - if err != nil { - t.Fatal(err) - } - - for _, file := range files { - // name := file.Name() - file := file - t.Run(file.Name(), func(t *testing.T) { - t.Parallel() - raw, err := os.ReadFile(filepath.Join(caseRoot, file.Name())) - require.NoError(t, err) - _, err = wiki.Parse(string(raw)) - require.NotNil(t, err, "expecting reporting error") - }) - } -} - -func TestAgainstValidSpec(t *testing.T) { - t.Parallel() - checkSubmodule(t) - var caseRoot = filepath.Join(testRoot, "valid") - files, err := os.ReadDir(caseRoot) - if err != nil { - t.Fatal(err) - } - for _, file := range files { //nolint:paralleltest - if strings.HasSuffix(file.Name(), ".wiki") { - name := strings.TrimSuffix(file.Name(), ".wiki") - t.Run(name, testCase(caseRoot, name)) - } - } -} - -func testCase(root, name string) func(*testing.T) { - return func(t *testing.T) { - t.Parallel() - raw, err := os.ReadFile(filepath.Join(root, name+".wiki")) - require.NoError(t, err) - - yamlRaw, err := os.ReadFile(filepath.Join(root, name+".yaml")) - require.NoError(t, err) - - expected := Result{} - require.NoError(t, yaml.Unmarshal(yamlRaw, &expected)) - - result, err := wiki.Parse(string(raw)) - require.NoError(t, err) - - require.Equal(t, expected.Wiki(), result) - } -} - -func checkSubmodule(t *testing.T) { - t.Helper() - if _, err := os.Stat(testRoot); err != nil && os.IsNotExist(err) { - t.Fatal("test data missing, do you forget to init git submodules?" + - "Try `git submodule update --init --recursive`") - } -} diff --git a/pkg/wiki/strings.go b/pkg/wiki/strings.go deleted file mode 100644 index 6c37ed6cb..000000000 --- a/pkg/wiki/strings.go +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package wiki - -import ( - "strings" -) - -const spaceStr = " \t" - -func trimSpace(s string) string { - return strings.Trim(s, spaceStr) -} - -func trimLeftSpace(s string) string { - return strings.TrimLeft(s, spaceStr) -} - -func trimRightSpace(s string) string { - return strings.TrimRight(s, spaceStr) -} - -func unifyEOL(s string) string { - s = strings.ReplaceAll(s, "\r\n", "\n") - - return s -} - -func processInput(s string) (string, int) { - offset := 2 - s = unifyEOL(s) - - for _, c := range s { - switch c { - case '\n': - offset++ - case ' ', '\t': - continue - default: - return strings.TrimSpace(s), offset - } - } - - return strings.TrimSpace(s), offset -} diff --git a/pkg/wiki/testdata/wiki-syntax-spec b/pkg/wiki/testdata/wiki-syntax-spec deleted file mode 160000 index fe7435e42..000000000 --- a/pkg/wiki/testdata/wiki-syntax-spec +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fe7435e425469184337b99b35b190548bf5e9cfa diff --git a/pkg/wiki/type.go b/pkg/wiki/type.go deleted file mode 100644 index 9920a8e07..000000000 --- a/pkg/wiki/type.go +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package wiki - -type Wiki struct { - Type string `json:"type"` - Fields []Field `json:"fields"` -} - -type Field struct { - Key string `json:"key"` - Value string `json:"value"` - Values []Item `json:"values"` - Array bool `json:"array"` - Null bool `json:"null,omitempty"` -} - -type Item struct { - Key string `json:"key"` - Value string `json:"value"` -} - -// NonZero return a wiki without empty fields and items. -func (w Wiki) NonZero() Wiki { - var wiki = Wiki{Type: w.Type, Fields: make([]Field, 0, len(w.Fields))} - for _, f := range w.Fields { - if f.Null { - continue - } - - if !f.Array { - wiki.Fields = append(wiki.Fields, f) - - continue - } - - if len(f.Values) == 0 { - continue - } - - var items []Item - for _, item := range f.Values { - if item.Value == "" { - continue - } - - items = append(items, item) - } - - wiki.Fields = append(wiki.Fields, Field{ - Key: f.Key, - Array: f.Array, - Values: items, - }) - } - - return wiki -} diff --git a/pkg/wiki/type_test.go b/pkg/wiki/type_test.go deleted file mode 100644 index 396cb708c..000000000 --- a/pkg/wiki/type_test.go +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package wiki_test - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/bangumi/server/pkg/wiki" -) - -func TestWiki_NonZero(t *testing.T) { - t.Parallel() - - w := wiki.Wiki{ - Type: "t", - Fields: []wiki.Field{ - {Key: "k", Value: "V", Values: nil, Array: false, Null: false}, - {Key: "", Value: "", Values: nil, Array: false, Null: true}, - {Key: "kk", Value: "", Values: []wiki.Item{ - {Key: "k1", Value: "v1"}, - {Key: "", Value: "v2"}, - {Key: "", Value: ""}, - }, Array: true, Null: false}, - {Key: "k", Value: "V", Values: nil, Array: false, Null: false}, - {Key: "kk", Value: "", Values: []wiki.Item{ - {Key: "k1", Value: "v1"}, - {Key: "", Value: "v2"}, - {Key: "", Value: ""}, - }, Array: true, Null: false}, - }, - } - require.Equal(t, wiki.Wiki{ - Type: "t", - Fields: []wiki.Field{ - {Key: "k", Value: "V", Values: nil, Array: false, Null: false}, - {Key: "kk", Value: "", Values: []wiki.Item{ - {Key: "k1", Value: "v1"}, - {Key: "", Value: "v2"}, - }, Array: true, Null: false}, - {Key: "k", Value: "V", Values: nil, Array: false, Null: false}, - {Key: "kk", Value: "", Values: []wiki.Item{ - {Key: "k1", Value: "v1"}, - {Key: "", Value: "v2"}, - }, Array: true, Null: false}, - }, - }, w.NonZero()) -} diff --git a/proto b/proto deleted file mode 160000 index 9433a45b3..000000000 --- a/proto +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9433a45b3d3a273870d9c3da2b8406622360f31e diff --git a/readme.md b/readme.md index c61b8f458..44bac5a21 100644 --- a/readme.md +++ b/readme.md @@ -1,18 +1,13 @@ 新后端服务器。 -![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/Bangumi/server?style=flat-square) -[![Codecov](https://img.shields.io/codecov/c/github/Bangumi/server?style=flat-square)](https://app.codecov.io/gh/Bangumi/server) - ## Requirements -- [Go 1.19](https://go.dev/) +- ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/Bangumi/server?style=flat-square) - [go-task](https://taskfile.dev/installation/),使用 `task` 查看所有的构建目标。 - [golangci-lint](https://golangci-lint.run/),使用 `task lint` 运行 linter。 ## Optional Requirements: -- [buf](https://buf.build/) 基于 ProtoBuffer 生成 grpc 相关文件。 -- [mockery](https://github.com/vektra/mockery) 生成 mock - nodejs: 用于生成 openapi 文件。 ## Init @@ -35,19 +30,12 @@ task - `MYSQL_PASS` 默认 `password` - `REDIS_URI` 默认 `redis://127.0.0.1:6379/0` - `HTTP_PORT` 默认 `3000` - -#### 微服务相关 - -- `ETCD_ADDR` etcd, 用于微服务的服务发现,留空(默认)的情况下各个微服务会使用 noop mock。example:`http://127.0.0.1:2379` -- `ETCD_NAMESPACE` etcd 服务注册的 key 前缀。大多数情况下不需要设置。 - -https://github.com/bangumi/service-timeline +- `KAFKA_BROKER` kafka broker 地址。 搜索功能相关的环境变量 - `MEILISEARCH_URL` meilisearch 地址,默认为空。不设置的话不会初始化搜索客户端。 - `MEILISEARCH_KEY` meilisearch key。 -- `KAFKA_BROKER` kafka broker 地址,启动 canal 需要 kafka。 你也可以把配置放在 `.env` 文件中,`go-task` 会自动加载 `.env` 文件中的环境变量。 @@ -65,7 +53,7 @@ MEILISEARCH_URL="http://127.0.0.1:7700/" 或者使用 yaml 格式的配置文件: -查看 config.example.yaml 或者 [config/config.go](https://github.com/bangumi/server/blob/master/config/config.go) +查看 config.example.toml 或者 [config/config.go](https://github.com/bangumi/server/blob/master/config/config.go) ## 开发 @@ -78,13 +66,13 @@ ORM: [GORM](https://github.com/go-gorm/gorm) 和 [GORM Gen](https://github.com/g 启动 HTTP server ```shell -go run main.go --config config.yaml web +task web ``` 启动 kafka consumer ```shell -go run main.go canal --config config.yaml +task consumer ``` ### 后端环境 @@ -93,10 +81,6 @@ redis 和 mysql 都在此 docker-compose 内 /\d/.test(key))); + +platforms[3] ??= { + 0: { + id: 0, + type: "", + type_cn: "", + alias: "", + wiki_tpl: "", + order: 0, + }, +}; + +fs.writeFileSync("pkg/vars/platform.go.json", JSON.stringify(platforms, null, 2)); + +fs.writeFileSync( + "pkg/vars/staffs.go.json", + JSON.stringify(yaml.parse(fs.readFileSync("pkg/vars/common/subject_staffs.yml", "utf-8")), null, 2), +); + +fs.writeFileSync( + "pkg/vars/relations.go.json", + JSON.stringify(yaml.parse(fs.readFileSync("pkg/vars/common/subject_relations.yml", "utf-8")), null, 2), +); diff --git a/scripts/changelog.js b/scripts/changelog.js deleted file mode 100644 index 956bd0946..000000000 --- a/scripts/changelog.js +++ /dev/null @@ -1,70 +0,0 @@ -const conventionalChangelog = require("conventional-changelog"); - -const commitTemplate = `*{{#if scope}} **{{scope}}**: -{{~/if}} {{#if subject}} - {{~subject}} -{{~else}} - {{~header}} -{{~/if}} - -`; - -const s = conventionalChangelog( - { - preset: "angular", - tagPrefix: "v", - releaseCount: 2, - }, - { linkReferences: false }, - undefined, - { - noteKeywords: ["BREAKING CHANGE", "BREAKING CHANGES"], - }, - { - headerPartial: "", - commitPartial: commitTemplate, - transform(commit) { - if (!commit.type) { - return false; - } - - if (commit.scope) { - if (["internal", "dal"].includes(commit.scope)) { - return false; - } - } - - if (!["feat", "fix", "perf", "revert"].includes(commit.type)) { - return false; - } - - if (commit.type === "feat") { - commit.type = "Features"; - } else if (commit.type === "fix") { - commit.type = "Bug Fixes"; - } else if (commit.type === "perf") { - commit.type = "Performance Improvements"; - } else if (commit.type === "revert" || commit.revert) { - commit.type = "Reverts"; - } - - return commit; - }, - } -); - -let changelog = ""; - -s.on("data", function (data) { - changelog += data.toString(); -}); - -s.on("end", function () { - changelog = changelog - .split("\n") - .map((value) => value.trim()) - .join("\n") - .trim(); - - console.log(changelog); -}); diff --git a/Taskfile.yaml b/taskfile.yaml similarity index 66% rename from Taskfile.yaml rename to taskfile.yaml index 3c1f34a52..ebeacc57d 100644 --- a/Taskfile.yaml +++ b/taskfile.yaml @@ -2,22 +2,12 @@ version: "3" dotenv: [".env", ".envrc"] -includes: - mock: "./etc/mock.task.yaml" - tasks: default: silent: true cmds: - task --list - install: - cmds: - - go install google.golang.org/protobuf/cmd/protoc-gen-go - - go install google.golang.org/grpc/cmd/protoc-gen-go-grpc - - go install github.com/vektra/mockery/v2 - - echo "please install buf by yourself https://docs.buf.build/installation" - build: desc: Build Web Server Binary sources: @@ -26,7 +16,7 @@ tasks: generates: - ./dist/chii.exe cmds: - - go build -ldflags '-w -s' -trimpath -o dist/chii.exe main.go + - go build -trimpath -o dist/chii.exe main.go env: CGO_ENABLED: "0" @@ -34,7 +24,7 @@ tasks: silent: true desc: Run 'golangci-lint' cmds: - - golangci-lint run --fix + - golangci-lint --path-prefix "{{ .TASKFILE_DIR }}" run --fix test: desc: Run mocked tests, need nothing. @@ -43,6 +33,27 @@ tasks: env: CGO_ENABLED: "0" + web: + desc: Run Web Server + cmds: + - task: build + - ./dist/chii.exe --config config.toml web + + consumer: + desc: Run Kafka Consumer + cmds: + - go run main.go canal --config config.toml + + openapi-test: + desc: Test OpenAPI Schema + cmds: + - yarn run test + + openapi: + desc: Build OpenAPI Schema + cmds: + - yarn run build + bench: desc: Run benchmark cmds: @@ -64,26 +75,22 @@ tasks: TEST_MYSQL: "1" TEST_REDIS: "1" - coverage: - desc: Run tests with coverage report, used in CI. + mod: + desc: "go mod tidy" cmds: - - go test -timeout 10s -tags test -race -coverpkg=./... -covermode=atomic -coverprofile=coverage.out ./... - env: - TEST_MYSQL: "1" - TEST_REDIS: "1" + - go mod tidy - # generated files gen: desc: Generate all generated GO files cmds: - - cmd: buf generate - task: gorm - task: mock mock: desc: Generate Mocks. - deps: - - mock:all + cmds: + - rm ./internal/mocks/ -rf + - go run github.com/vektra/mockery/v3 --config ./.mockery.yaml gorm: desc: Run gorm-gen to generate go struct from mysql database. @@ -97,5 +104,6 @@ tasks: clean: cmds: + - rm -rf ./dist/ - rm -rf .task - rm -rf .bin diff --git a/web/accessor/accesor.go b/web/accessor/accesor.go index 354dea0c1..fbac1ba8a 100644 --- a/web/accessor/accesor.go +++ b/web/accessor/accesor.go @@ -15,7 +15,7 @@ package accessor import ( - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -34,7 +34,7 @@ func (a *Accessor) AllowNSFW() bool { return a.Login && a.Auth.AllowNSFW() } -func (a *Accessor) fillBasicInfo(c echo.Context) { +func (a *Accessor) fillBasicInfo(c *echo.Context) { a.Login = false a.RequestID = c.Request().Header.Get(cf.HeaderRequestID) a.IP = c.RealIP() @@ -53,7 +53,7 @@ func (a Accessor) MarshalLogObject(encoder zapcore.ObjectEncoder) error { encoder.AddString("id", a.RequestID) encoder.AddString("IP", a.IP) if a.Login { - encoder.AddUint32("user_id", a.Auth.ID) + encoder.AddUint32("user_id", a.ID) } return nil } diff --git a/web/accessor/ctx.go b/web/accessor/ctx.go index 1d4e8fcf7..5212ed65e 100644 --- a/web/accessor/ctx.go +++ b/web/accessor/ctx.go @@ -15,20 +15,20 @@ package accessor import ( - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "go.uber.org/zap" "github.com/bangumi/server/internal/pkg/logger" "github.com/bangumi/server/web/internal/ctxkey" ) -func NewFromCtx(c echo.Context) *Accessor { +func NewFromCtx(c *echo.Context) *Accessor { a := get() a.fillBasicInfo(c) return a } -func GetFromCtx(c echo.Context) *Accessor { +func GetFromCtx(c *echo.Context) *Accessor { raw := c.Get(ctxkey.User) if raw == nil { return NewFromCtx(c) diff --git a/web/cookie/clear.go b/web/cookie/clear.go index 63819183f..3a9609b82 100644 --- a/web/cookie/clear.go +++ b/web/cookie/clear.go @@ -17,9 +17,9 @@ package cookie import ( "net/http" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" ) -func Clear(c echo.Context, key string) { +func Clear(c *echo.Context, key string) { c.SetCookie(&http.Cookie{Name: key, Value: "", SameSite: http.SameSiteLaxMode, HttpOnly: true}) } diff --git a/web/dev.go b/web/dev.go index 8d3097a8e..5761cb674 100644 --- a/web/dev.go +++ b/web/dev.go @@ -15,17 +15,14 @@ package web import ( - "net/http" - "net/http/pprof" - - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/bangumi/server/internal/pkg/random" "github.com/bangumi/server/web/req/cf" ) func genFakeRequestID(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { + return func(c *echo.Context) error { devRequestID := "fake-ray-" + random.Base62String(10) c.Request().Header.Set(cf.HeaderRequestID, devRequestID) c.Set(cf.HeaderRequestID, devRequestID) @@ -33,11 +30,3 @@ func genFakeRequestID(next echo.HandlerFunc) echo.HandlerFunc { return next(c) } } - -func addProfile(app *echo.Echo) { - app.GET("/debug/pprof/cmdline", echo.WrapHandler(http.HandlerFunc(pprof.Cmdline))) - app.GET("/debug/pprof/profile", echo.WrapHandler(http.HandlerFunc(pprof.Profile))) - app.GET("/debug/pprof/symbol", echo.WrapHandler(http.HandlerFunc(pprof.Symbol))) - app.GET("/debug/pprof/trace", echo.WrapHandler(http.HandlerFunc(pprof.Trace))) - app.Any("/debug/pprof/", echo.WrapHandler(http.HandlerFunc(pprof.Index))) -} diff --git a/web/error.go b/web/error.go index 94daeff37..2c2613c7c 100644 --- a/web/error.go +++ b/web/error.go @@ -15,10 +15,11 @@ package web import ( + "context" "errors" "net/http" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -28,7 +29,7 @@ import ( "github.com/bangumi/server/web/util" ) -func globalNotFoundHandler(c echo.Context) error { +func globalNotFoundHandler(c *echo.Context) error { return c.JSON(http.StatusNotFound, res.Error{ Title: "Not Found", Description: "This is default response, if you see this response, please check your request", @@ -36,11 +37,14 @@ func globalNotFoundHandler(c echo.Context) error { }) } +//nolint:funlen func getDefaultErrorHandler() echo.HTTPErrorHandler { var log = logger.Named("http.err"). WithOptions(zap.AddStacktrace(zapcore.PanicLevel), zap.WithCaller(false)) - return func(err error, c echo.Context) { + return func(c *echo.Context, err error) { + reqID := c.Request().Header.Get(cf.HeaderRequestID) + { var e res.HTTPError if errors.As(err, &e) { @@ -48,6 +52,7 @@ func getDefaultErrorHandler() echo.HTTPErrorHandler { _ = c.JSON(e.Code, res.Error{ Title: http.StatusText(e.Code), Description: e.Msg, + RequestID: reqID, Details: util.Detail(c), }) return @@ -60,25 +65,33 @@ func getDefaultErrorHandler() echo.HTTPErrorHandler { log.Error("unexpected echo error", zap.Int("code", e.Code), zap.Any("message", e.Message), - zap.String("path", c.Request().URL.Path), - zap.String("query", c.Request().URL.RawQuery), - zap.String("cf-ray", c.Request().Header.Get(cf.HeaderRequestID)), + zap.String("request_method", c.Request().Method), + zap.String("request_uri", c.Request().URL.Path), + zap.String("request_query", c.Request().URL.RawQuery), + zap.String("request_id", reqID), ) _ = c.JSON(http.StatusInternalServerError, res.Error{ Title: http.StatusText(e.Code), Description: e.Error(), + RequestID: reqID, Details: util.DetailWithErr(c, err), }) return } } + if errors.Is(err, context.Canceled) { + _ = c.NoContent(http.StatusNoContent) + return + } + log.Error("unexpected error", zap.Error(err), - zap.String("path", c.Path()), - zap.String("query", c.Request().URL.RawQuery), - zap.String("cf-ray", c.Request().Header.Get(cf.HeaderRequestID)), + zap.String("request_method", c.Request().Method), + zap.String("request_uri", c.Request().URL.Path), + zap.String("request_query", c.Request().URL.RawQuery), + zap.String("request_id", reqID), ) // unexpected error, return internal server error diff --git a/web/handler/character/character.go b/web/handler/character/character.go index 2d42cfa2f..eacf54fd4 100644 --- a/web/handler/character/character.go +++ b/web/handler/character/character.go @@ -20,62 +20,36 @@ import ( "github.com/bangumi/server/config" "github.com/bangumi/server/ctrl" "github.com/bangumi/server/internal/character" - "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/collections" "github.com/bangumi/server/internal/person" - "github.com/bangumi/server/internal/pkg/compat" - "github.com/bangumi/server/internal/pkg/null" "github.com/bangumi/server/internal/subject" - "github.com/bangumi/server/pkg/wiki" - "github.com/bangumi/server/web/res" ) type Character struct { - ctrl ctrl.Ctrl - person person.Service - c character.Repo - subject subject.Repo - log *zap.Logger - cfg config.AppConfig + ctrl ctrl.Ctrl + person person.Service + character character.Repo + subject subject.Repo + collect collections.Repo + log *zap.Logger + cfg config.AppConfig } func New( - p person.Service, + person person.Service, ctrl ctrl.Ctrl, - c character.Repo, + character character.Repo, subject subject.Repo, + collect collections.Repo, log *zap.Logger, ) (Character, error) { return Character{ - ctrl: ctrl, - c: c, - subject: subject, - person: p, - log: log.Named("handler.Character"), - cfg: config.AppConfig{}, + ctrl: ctrl, + character: character, + subject: subject, + person: person, + collect: collect, + log: log.Named("handler.Character"), + cfg: config.AppConfig{}, }, nil } - -func convertModelCharacter(s model.Character) res.CharacterV0 { - img := res.PersonImage(s.Image) - - return res.CharacterV0{ - ID: s.ID, - Type: s.Type, - Name: s.Name, - NSFW: s.NSFW, - Images: img, - Summary: s.Summary, - Infobox: compat.V0Wiki(wiki.ParseOmitError(s.Infobox).NonZero()), - Gender: null.NilString(res.GenderMap[s.FieldGender]), - BloodType: null.NilUint8(s.FieldBloodType), - BirthYear: null.NilUint16(s.FieldBirthYear), - BirthMon: null.NilUint8(s.FieldBirthMon), - BirthDay: null.NilUint8(s.FieldBirthDay), - Stat: res.Stat{ - Comments: s.CommentCount, - Collects: s.CollectCount, - }, - Redirect: s.Redirect, - Locked: s.Locked, - } -} diff --git a/web/handler/character/character_test.go b/web/handler/character/character_test.go index 305c2b06a..8c19cd638 100644 --- a/web/handler/character/character_test.go +++ b/web/handler/character/character_test.go @@ -19,7 +19,7 @@ import ( "testing" "time" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/trim21/htest" @@ -92,7 +92,6 @@ func TestCharacter_GetImage(t *testing.T) { app := test.GetWebApp(t, test.Mock{CharacterRepo: m}) for _, imageType := range []string{"large", "grid", "medium", "small"} { - imageType := imageType t.Run(imageType, func(t *testing.T) { t.Parallel() diff --git a/web/handler/character/collect.go b/web/handler/character/collect.go new file mode 100644 index 000000000..ab89d04a3 --- /dev/null +++ b/web/handler/character/collect.go @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +package character + +import ( + "errors" + + "github.com/labstack/echo/v5" + + "github.com/bangumi/server/domain/gerr" + "github.com/bangumi/server/internal/collections/domain/collection" + "github.com/bangumi/server/web/accessor" + "github.com/bangumi/server/web/req" + "github.com/bangumi/server/web/res" +) + +func (h Character) CollectCharacter(c *echo.Context) error { + cid, err := req.ParseID(c.Param("id")) + if err != nil { + return err + } + uid := accessor.GetFromCtx(c).ID + return h.collectCharacter(c, cid, uid) +} + +func (h Character) UncollectCharacter(c *echo.Context) error { + cid, err := req.ParseID(c.Param("id")) + if err != nil { + return err + } + uid := accessor.GetFromCtx(c).ID + return h.uncollectCharacter(c, cid, uid) +} + +func (h Character) collectCharacter(c *echo.Context, cid uint32, uid uint32) error { + ctx := c.Request().Context() + // check if the character exists + if _, err := h.character.Get(ctx, cid); err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.ErrNotFound + } + return res.InternalError(c, err, "get character error") + } + // check if the user has collected the character + if _, err := h.collect.GetPersonCollection(ctx, uid, collection.PersonCollectCategoryCharacter, cid); err == nil { + return nil // already collected + } else if !errors.Is(err, gerr.ErrNotFound) { + return res.InternalError(c, err, "get character collect error") + } + // add the collect + if err := h.collect.AddPersonCollection(ctx, uid, collection.PersonCollectCategoryCharacter, cid); err != nil { + return res.InternalError(c, err, "add character collect failed") + } + return nil +} + +func (h Character) uncollectCharacter(c *echo.Context, cid uint32, uid uint32) error { + ctx := c.Request().Context() + // check if the character exists + if _, err := h.character.Get(ctx, cid); err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.ErrNotFound + } + return res.InternalError(c, err, "get character error") + } + // check if the user has collected the character + if _, err := h.collect.GetPersonCollection(ctx, uid, collection.PersonCollectCategoryCharacter, cid); err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.NotFound("character not collected") + } + return res.InternalError(c, err, "get character collect error") + } + // remove the collect + if err := h.collect.RemovePersonCollection(ctx, uid, collection.PersonCollectCategoryCharacter, cid); err != nil { + return res.InternalError(c, err, "remove character collect failed") + } + return nil +} diff --git a/web/handler/character/get.go b/web/handler/character/get.go index 5def90ca7..551c11a4d 100644 --- a/web/handler/character/get.go +++ b/web/handler/character/get.go @@ -18,8 +18,9 @@ import ( "errors" "net/http" "strconv" + "time" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/trim21/errgo" "github.com/bangumi/server/domain/gerr" @@ -29,14 +30,14 @@ import ( "github.com/bangumi/server/web/res" ) -func (h Character) Get(c echo.Context) error { +func (h Character) Get(c *echo.Context) error { u := accessor.GetFromCtx(c) id, err := req.ParseID(c.Param("id")) if err != nil { return err } - r, err := h.c.Get(c.Request().Context(), id) + r, err := h.character.Get(c.Request().Context(), id) if err != nil { if errors.Is(err, gerr.ErrNotFound) { return res.ErrNotFound @@ -53,16 +54,20 @@ func (h Character) Get(c echo.Context) error { return res.ErrNotFound } - return c.JSON(http.StatusOK, convertModelCharacter(r)) + if !r.NSFW { + res.SetCacheControl(c, res.CacheControlParams{Public: true, MaxAge: time.Hour}) + } + + return c.JSON(http.StatusOK, res.ConvertModelCharacter(r)) } -func (h Character) GetImage(c echo.Context) error { +func (h Character) GetImage(c *echo.Context) error { id, err := req.ParseID(c.Param("id")) if err != nil { return err } - p, err := h.c.Get(c.Request().Context(), id) + p, err := h.character.Get(c.Request().Context(), id) if err != nil { if errors.Is(err, gerr.ErrNotFound) { return res.ErrNotFound @@ -75,6 +80,8 @@ func (h Character) GetImage(c echo.Context) error { return res.BadRequest("bad image type: " + c.QueryParam("type")) } + res.SetCacheControl(c, res.CacheControlParams{Public: true, MaxAge: time.Hour * 24}) + if l == "" { return c.Redirect(http.StatusFound, res.DefaultImageURL) } diff --git a/web/handler/character/get_related_persons.go b/web/handler/character/get_related_persons.go index 106d216c9..767b13770 100644 --- a/web/handler/character/get_related_persons.go +++ b/web/handler/character/get_related_persons.go @@ -17,8 +17,9 @@ package character import ( "errors" "net/http" + "time" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/trim21/errgo" "github.com/bangumi/server/domain/gerr" @@ -27,13 +28,13 @@ import ( "github.com/bangumi/server/web/res" ) -func (h Character) GetRelatedPersons(c echo.Context) error { +func (h Character) GetRelatedPersons(c *echo.Context) error { id, err := req.ParseID(c.Param("id")) if err != nil { return err } - _, err = h.c.Get(c.Request().Context(), id) + _, err = h.character.Get(c.Request().Context(), id) if err != nil { if errors.Is(err, gerr.ErrNotFound) { return res.ErrNotFound @@ -61,13 +62,16 @@ func (h Character) GetRelatedPersons(c echo.Context) error { ID: cast.Person.ID, Name: cast.Person.Name, Type: cast.Person.Type, - Images: res.PersonImage(cast.Subject.Image), + Images: res.PersonImage(cast.Person.Image), SubjectID: cast.Subject.ID, + SubjectType: cast.Subject.TypeID, SubjectName: cast.Subject.Name, SubjectNameCn: cast.Subject.NameCN, Staff: res.CharacterStaffString(mSubjectRelations[cast.Subject.ID]), } } + res.SetCacheControl(c, res.CacheControlParams{Public: true, MaxAge: time.Hour}) + return c.JSON(http.StatusOK, response) } diff --git a/web/handler/character/get_related_subjects.go b/web/handler/character/get_related_subjects.go index 9ed59b9c4..c83535412 100644 --- a/web/handler/character/get_related_subjects.go +++ b/web/handler/character/get_related_subjects.go @@ -18,8 +18,9 @@ import ( "context" "errors" "net/http" + "time" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/trim21/errgo" "github.com/bangumi/server/domain" @@ -31,7 +32,7 @@ import ( "github.com/bangumi/server/web/res" ) -func (h Character) GetRelatedSubjects(c echo.Context) error { +func (h Character) GetRelatedSubjects(c *echo.Context) error { id, err := req.ParseID(c.Param("id")) if err != nil { return err @@ -50,6 +51,7 @@ func (h Character) GetRelatedSubjects(c echo.Context) error { s := relation.Subject response[i] = res.CharacterRelatedSubject{ ID: s.ID, + Type: s.TypeID, Name: s.Name, NameCn: s.NameCN, Staff: res.CharacterStaffString(relation.TypeID), @@ -57,6 +59,8 @@ func (h Character) GetRelatedSubjects(c echo.Context) error { } } + res.SetCacheControl(c, res.CacheControlParams{Public: true, MaxAge: time.Hour}) + return c.JSON(http.StatusOK, response) } @@ -64,7 +68,7 @@ func (h Character) getCharacterRelatedSubjects( ctx context.Context, characterID model.CharacterID, ) (model.Character, []model.SubjectCharacterRelation, error) { - character, err := h.c.Get(ctx, characterID) + character, err := h.character.Get(ctx, characterID) if err != nil { return model.Character{}, nil, errgo.Wrap(err, "get character") } diff --git a/web/handler/common/access_token.go b/web/handler/common/access_token.go index 9dadd76d4..4e9e64152 100644 --- a/web/handler/common/access_token.go +++ b/web/handler/common/access_token.go @@ -18,7 +18,7 @@ import ( "errors" "strings" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/trim21/errgo" "github.com/bangumi/server/domain/gerr" @@ -29,7 +29,7 @@ import ( ) func (h Common) MiddlewareAccessTokenAuth(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { + return func(c *echo.Context) error { var a = accessor.NewFromCtx(c) defer a.Free() diff --git a/web/handler/common/common.go b/web/handler/common/common.go index c8a448460..7a127d6db 100644 --- a/web/handler/common/common.go +++ b/web/handler/common/common.go @@ -21,13 +21,11 @@ import ( "github.com/bangumi/server/config" "github.com/bangumi/server/internal/auth" - "github.com/bangumi/server/web/session" ) func New( log *zap.Logger, auth auth.Service, - session session.Manager, config config.AppConfig, ) (Common, error) { validate, trans, err := getValidator() @@ -38,7 +36,6 @@ func New( log = log.Named("handler.Common") return Common{ Config: config, - session: session, auth: auth, log: log, skip1Log: log.WithOptions(zap.AddCallerSkip(1)), @@ -52,7 +49,6 @@ type Common struct { auth auth.Service skip1Log *zap.Logger log *zap.Logger - session session.Manager V *validator.Validate validatorTranslation ut.Translator } diff --git a/web/handler/common/session.go b/web/handler/common/session.go deleted file mode 100644 index aac0835fd..000000000 --- a/web/handler/common/session.go +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package common - -import ( - "errors" - "net/http" - - "github.com/labstack/echo/v4" - "github.com/trim21/errgo" - "go.uber.org/zap" - - "github.com/bangumi/server/domain/gerr" - "github.com/bangumi/server/web/accessor" - "github.com/bangumi/server/web/cookie" - "github.com/bangumi/server/web/internal/ctxkey" - "github.com/bangumi/server/web/res" - "github.com/bangumi/server/web/session" - "github.com/bangumi/server/web/util" -) - -func (h Common) MiddlewareSessionAuth(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - var a = accessor.NewFromCtx(c) - defer a.Free() - - co, err := c.Cookie(session.CookieKey) - if err != nil { - return errgo.Wrap(err, "get cookie") - } - - if co.Value != "" { - s, err := h.getSession(c, co.Value) - if err != nil { - if errors.Is(err, session.ErrExpired) || errors.Is(err, gerr.ErrNotFound) { - cookie.Clear(c, session.CookieKey) - goto Next - } - - h.log.Error("failed to get session", zap.Error(err), a.Log()) - return c.JSON(http.StatusInternalServerError, - res.Error{ - Title: "internal server error", - Details: util.DetailWithErr(c, err), - Description: "failed to read session, please try clear your browser cookies and re-try", - }) - } - - auth, err := h.auth.GetByID(c.Request().Context(), s.UserID) - if err != nil { - return errgo.Wrap(err, "failed to user with permission") - } - - a.SetAuth(auth) - } - - Next: - c.Set(ctxkey.User, a) - - return next(c) - } -} - -func (h Common) getSession(c echo.Context, value string) (session.Session, error) { - s, err := h.session.Get(c.Request().Context(), value) - if err != nil { - return session.Session{}, errgo.Wrap(err, "sessionManager.get") - } - - return s, nil -} diff --git a/web/handler/common/validation.go b/web/handler/common/validation.go index ff4658a5e..788d5faee 100644 --- a/web/handler/common/validation.go +++ b/web/handler/common/validation.go @@ -25,13 +25,13 @@ import ( ut "github.com/go-playground/universal-translator" "github.com/go-playground/validator/v10" zhTranslations "github.com/go-playground/validator/v10/translations/zh" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/trim21/errgo" "github.com/bangumi/server/web/res" ) -func (h Common) ValidationError(c echo.Context, err error) error { +func (h Common) ValidationError(c *echo.Context, err error) error { return c.JSON(http.StatusBadRequest, res.Error{ Title: "Bad Request", Description: "can't validate request body", diff --git a/web/handler/episode.go b/web/handler/episode.go index 60fa9e8a7..203fbcba6 100644 --- a/web/handler/episode.go +++ b/web/handler/episode.go @@ -19,7 +19,7 @@ import ( "net/http" "strconv" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/trim21/errgo" "github.com/bangumi/server/domain/gerr" @@ -32,7 +32,7 @@ import ( "github.com/bangumi/server/web/res" ) -func (h Handler) GetEpisode(c echo.Context) error { +func (h Handler) GetEpisode(c *echo.Context) error { u := accessor.GetFromCtx(c) id, err := req.ParseID(c.Param("id")) @@ -63,7 +63,7 @@ func (h Handler) GetEpisode(c echo.Context) error { return c.JSON(http.StatusOK, res.ConvertModelEpisode(e)) } -func (h Handler) ListEpisode(c echo.Context) error { +func (h Handler) ListEpisode(c *echo.Context) error { u := accessor.GetFromCtx(c) page, err := req.GetPageQuery(c, req.EpisodeDefaultLimit, req.EpisodeMaxLimit) diff --git a/web/handler/fx.go b/web/handler/fx.go index c1256503a..44d20b6f9 100644 --- a/web/handler/fx.go +++ b/web/handler/fx.go @@ -20,9 +20,7 @@ import ( "github.com/bangumi/server/web/handler/character" "github.com/bangumi/server/web/handler/common" "github.com/bangumi/server/web/handler/index" - "github.com/bangumi/server/web/handler/notification" "github.com/bangumi/server/web/handler/person" - "github.com/bangumi/server/web/handler/pm" "github.com/bangumi/server/web/handler/subject" "github.com/bangumi/server/web/handler/user" ) @@ -36,7 +34,5 @@ var Module = fx.Module("handler", subject.New, character.New, index.New, - pm.New, - notification.New, ), ) diff --git a/web/handler/index/collect.go b/web/handler/index/collect.go index f8b7003bd..afd95a6a1 100644 --- a/web/handler/index/collect.go +++ b/web/handler/index/collect.go @@ -17,7 +17,7 @@ package index import ( "errors" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/bangumi/server/domain/gerr" "github.com/bangumi/server/web/accessor" @@ -25,64 +25,61 @@ import ( "github.com/bangumi/server/web/res" ) -func (h *Handler) CollectIndex(c echo.Context) error { +func (h *Handler) CollectIndex(c *echo.Context) error { iid, err := req.ParseID(c.Param("id")) if err != nil { return err } - uid := accessor.GetFromCtx(c).ID - return h.collectIndex(c, iid, uid) + user := accessor.GetFromCtx(c) + return h.collectIndex(c, iid, user) } -func (h *Handler) UncollectIndex(c echo.Context) error { +func (h *Handler) UncollectIndex(c *echo.Context) error { iid, err := req.ParseID(c.Param("id")) if err != nil { return err } - uid := accessor.GetFromCtx(c).ID - return h.uncollectIndex(c, iid, uid) + user := accessor.GetFromCtx(c) + return h.uncollectIndex(c, iid, user) } -func (h *Handler) collectIndex(c echo.Context, indexID uint32, uid uint32) error { +func (h *Handler) collectIndex(c *echo.Context, indexID uint32, user *accessor.Accessor) error { ctx := c.Request().Context() - // check if the index exists - if _, err := h.i.Get(ctx, indexID); err != nil { - if errors.Is(err, gerr.ErrNotFound) { - return res.NotFound("index not found") - } + + if _, ok, err := h.getIndexWithCache(ctx, user, indexID); err != nil { return res.InternalError(c, err, "get index error") + } else if !ok { + return res.NotFound("index not found") } // check if the user has collected the index - if _, err := h.i.GetIndexCollect(ctx, indexID, uid); err == nil { + if _, err := h.i.GetIndexCollect(ctx, indexID, user.ID); err == nil { return nil // already collected } else if !errors.Is(err, gerr.ErrNotFound) { return res.InternalError(c, err, "get index collect error") } // add the collect - if err := h.i.AddIndexCollect(ctx, indexID, uid); err != nil { + if err := h.i.AddIndexCollect(ctx, indexID, user.ID); err != nil { return res.InternalError(c, err, "add index collect failed") } return nil } -func (h *Handler) uncollectIndex(c echo.Context, indexID uint32, uid uint32) error { +func (h *Handler) uncollectIndex(c *echo.Context, indexID uint32, user *accessor.Accessor) error { ctx := c.Request().Context() - // check if the index exists - if _, err := h.i.Get(ctx, indexID); err != nil { - if errors.Is(err, gerr.ErrNotFound) { - return res.NotFound("index not found") - } + if _, ok, err := h.getIndexWithCache(ctx, user, indexID); err != nil { return res.InternalError(c, err, "get index error") + } else if !ok { + return res.NotFound("index not found") } // check if the user has collected the index - if _, err := h.i.GetIndexCollect(ctx, indexID, uid); err != nil { + if _, err := h.i.GetIndexCollect(ctx, indexID, user.ID); err != nil { if errors.Is(err, gerr.ErrNotFound) { return res.NotFound("index not collected") } return res.InternalError(c, err, "get index collect error") } // delete the collect - if err := h.i.DeleteIndexCollect(ctx, indexID, uid); err != nil { + if err := h.i.DeleteIndexCollect(ctx, indexID, user.ID); err != nil { return res.InternalError(c, err, "delete index collect failed") } return nil diff --git a/web/handler/index/collect_test.go b/web/handler/index/collect_test.go index 26911976c..cc5604883 100644 --- a/web/handler/index/collect_test.go +++ b/web/handler/index/collect_test.go @@ -18,7 +18,7 @@ import ( "net/http" "testing" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/trim21/htest" @@ -67,3 +67,23 @@ func TestUncollectIndex(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) } + +func TestCollectIndex_PrivateNotOwner(t *testing.T) { + t.Parallel() + mockIndex := mocks.NewIndexRepo(t) + mockIndex.EXPECT().Get(mock.Anything, uint32(233)).Return( + model.Index{ID: 233, CreatorID: 1, Privacy: model.IndexPrivacyPrivate}, + nil, + ) + mockAuth := mocks.NewAuthRepo(t) + mockAuth.EXPECT().GetByToken(mock.Anything, mock.Anything).Return(auth.UserInfo{ID: 6}, nil) + mockAuth.EXPECT().GetPermission(mock.Anything, mock.Anything).Return(auth.Permission{}, nil) + + app := test.GetWebApp(t, test.Mock{IndexRepo: mockIndex, AuthRepo: mockAuth}) + + resp := htest.New(t, app). + Header(echo.HeaderAuthorization, "Bearer token"). + Post("/v0/indices/233/collect") + + require.Equal(t, http.StatusNotFound, resp.StatusCode) +} diff --git a/web/handler/index/index.go b/web/handler/index/index.go index 8bfc2fdf3..f39e33dac 100644 --- a/web/handler/index/index.go +++ b/web/handler/index/index.go @@ -20,7 +20,7 @@ import ( "net/http" "time" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/trim21/errgo" "go.uber.org/zap" @@ -32,7 +32,13 @@ import ( "github.com/bangumi/server/web/res" ) -func (h Handler) GetIndex(c echo.Context) error { +type indexCacheValue struct { + Index res.Index `json:"index"` + Privacy model.IndexPrivacy `json:"privacy"` + CreatorID model.UserID `json:"creator_id"` +} + +func (h Handler) GetIndex(c *echo.Context) error { user := accessor.GetFromCtx(c) id, err := req.ParseID(c.Param("id")) @@ -40,56 +46,113 @@ func (h Handler) GetIndex(c echo.Context) error { return err } - r, ok, err := h.getIndexWithCache(c.Request().Context(), id) + resp, ok, err := h.getIndexWithCache(c.Request().Context(), user, id) if err != nil { return errgo.Wrap(err, "failed to get index") } - if !ok || r.NSFW && !user.AllowNSFW() { + if !ok { return res.NotFound("index not found") } - return c.JSON(http.StatusOK, r) + return c.JSON(http.StatusOK, resp) } -func (h Handler) getIndexWithCache(c context.Context, id uint32) (res.Index, bool, error) { - var key = cachekey.Index(id) +func (h Handler) getIndexWithCache(ctx context.Context, user *accessor.Accessor, id uint32) (res.Index, bool, error) { + key := cachekey.Index(id) + + userID, allowNSFW := h.extractUserPrefs(user) + + if cached, ok, err := h.getIndexFromCache(ctx, key, userID, allowNSFW); err != nil || ok { + return cached, ok, err + } + + item, ok, err := h.buildIndexResponse(ctx, id, userID, allowNSFW) + if err != nil || !ok { + return item.Index, ok, err + } + + if item.Privacy == model.IndexPrivacyPublic { + _ = h.cache.Set(ctx, key, item, time.Hour) + } - var r res.Index - ok, err := h.cache.Get(c, key, &r) + return item.Index, true, nil +} + +func (h Handler) getIndexFromCache( + ctx context.Context, key string, userID model.UserID, allowNSFW bool, +) (res.Index, bool, error) { + var cached indexCacheValue + ok, err := h.cache.Get(ctx, key, &cached) if err != nil { - return r, ok, errgo.Wrap(err, "cache.Get") + return res.Index{}, ok, errgo.Wrap(err, "cache.Get") + } + + if !ok { + return res.Index{}, false, nil } - if ok { - return r, ok, nil + if !isIndexVisible(cached.Privacy, cached.CreatorID, userID) { + return res.Index{}, false, nil } + if cached.Index.NSFW && !allowNSFW { + return res.Index{}, false, nil + } + + return cached.Index, true, nil +} - i, err := h.i.Get(c, id) +func (h Handler) buildIndexResponse( + ctx context.Context, id uint32, userID model.UserID, allowNSFW bool, +) (indexCacheValue, bool, error) { + i, err := h.i.Get(ctx, id) if err != nil { if errors.Is(err, gerr.ErrNotFound) { - return res.Index{}, false, nil + return indexCacheValue{}, false, nil } - return res.Index{}, false, errgo.Wrap(err, "Index.Get") + return indexCacheValue{}, false, errgo.Wrap(err, "Index.Get") } - u, err := h.u.GetByID(c, i.CreatorID) + if !isIndexVisible(i.Privacy, i.CreatorID, userID) { + return indexCacheValue{}, false, nil + } + + u, err := h.u.GetByID(ctx, i.CreatorID) if err != nil { if errors.Is(err, gerr.ErrNotFound) { h.log.Error("index missing creator", zap.Uint32("index_id", id), zap.Uint32("creator", i.CreatorID)) } - return res.Index{}, false, errgo.Wrap(err, "failed to get creator: user.GetByID") + return indexCacheValue{}, false, errgo.Wrap(err, "failed to get creator: user.GetByID") + } + + r := res.IndexModelToResponse(&i, u) + if r.NSFW && !allowNSFW { + return indexCacheValue{}, false, nil } - r = res.IndexModelToResponse(&i, u) + return indexCacheValue{Index: r, Privacy: i.Privacy, CreatorID: i.CreatorID}, true, nil +} - _ = h.cache.Set(c, key, r, time.Hour) +func isIndexVisible(privacy model.IndexPrivacy, creatorID, userID model.UserID) bool { + if privacy == model.IndexPrivacyDeleted { + return false + } + if privacy == model.IndexPrivacyPrivate && creatorID != userID { + return false + } + return true +} + +func (h Handler) extractUserPrefs(user *accessor.Accessor) (model.UserID, bool) { + if user == nil { + return 0, false + } - return r, true, nil + return user.ID, user.AllowNSFW() } -func (h Handler) GetIndexSubjects(c echo.Context) error { +func (h Handler) GetIndexSubjects(c *echo.Context) error { user := accessor.GetFromCtx(c) id, err := req.ParseID(c.Param("id")) @@ -107,12 +170,12 @@ func (h Handler) GetIndexSubjects(c echo.Context) error { return err } - r, ok, err := h.getIndexWithCache(c.Request().Context(), id) + _, ok, err := h.getIndexWithCache(c.Request().Context(), user, id) if err != nil { return errgo.Wrap(err, "failed to get index") } - if !ok || (r.NSFW && !user.AllowNSFW()) { + if !ok { return res.ErrNotFound } @@ -120,7 +183,7 @@ func (h Handler) GetIndexSubjects(c echo.Context) error { } func (h Handler) getIndexSubjects( - c echo.Context, id model.IndexID, subjectType uint8, page req.PageQuery, + c *echo.Context, id model.IndexID, subjectType uint8, page req.PageQuery, ) error { count, err := h.i.CountSubjects(c.Request().Context(), id, subjectType) if err != nil { @@ -158,7 +221,7 @@ func (h Handler) getIndexSubjects( }) } -func (h Handler) NewIndex(c echo.Context) error { +func (h Handler) NewIndex(c *echo.Context) error { var reqData req.IndexBasicInfo if err := c.Echo().JSONSerializer.Deserialize(c, &reqData); err != nil { return res.JSONError(c, err) @@ -178,7 +241,6 @@ func (h Handler) NewIndex(c echo.Context) error { Total: 0, Comments: 0, Collects: 0, - Ban: false, NSFW: false, } ctx := c.Request().Context() @@ -194,7 +256,7 @@ func (h Handler) NewIndex(c echo.Context) error { } // 确保目录存在, 并且当前请求的用户持有权限. -func (h Handler) ensureIndexPermission(c echo.Context, indexID uint32) (*model.Index, error) { +func (h Handler) ensureIndexPermission(c *echo.Context, indexID uint32) (*model.Index, error) { accessor := accessor.GetFromCtx(c) index, err := h.i.Get(c.Request().Context(), indexID) if err != nil { @@ -209,7 +271,7 @@ func (h Handler) ensureIndexPermission(c echo.Context, indexID uint32) (*model.I return &index, nil } -func (h Handler) UpdateIndex(c echo.Context) error { +func (h Handler) UpdateIndex(c *echo.Context) error { indexID, err := req.ParseID(c.Param("id")) if err != nil { return err diff --git a/web/handler/index/index_test.go b/web/handler/index/index_test.go index a7bf3916a..399d77c86 100644 --- a/web/handler/index/index_test.go +++ b/web/handler/index/index_test.go @@ -19,7 +19,7 @@ import ( "testing" "time" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/trim21/htest" @@ -55,6 +55,59 @@ func TestHandler_GetIndex_NSFW(t *testing.T) { require.Equal(t, http.StatusNotFound, resp.StatusCode) } +func TestHandler_GetIndex_PrivateForOwner(t *testing.T) { + t.Parallel() + m := mocks.NewIndexRepo(t) + m.EXPECT().Get(mock.Anything, uint32(7)).Return( + model.Index{ID: 7, CreatorID: 6, Privacy: model.IndexPrivacyPrivate}, + nil, + ) + + mAuth := mocks.NewAuthRepo(t) + mAuth.EXPECT().GetByToken(mock.Anything, mock.Anything). + Return(auth.UserInfo{ID: 6, RegTime: time.Unix(1e9, 0)}, nil) + mAuth.EXPECT().GetPermission(mock.Anything, mock.Anything). + Return(auth.Permission{}, nil) + + app := test.GetWebApp(t, test.Mock{IndexRepo: m, AuthRepo: mAuth}) + + resp := htest.New(t, app). + Header(echo.HeaderAuthorization, "Bearer token"). + Get("/v0/indices/7") + + require.Equal(t, http.StatusOK, resp.StatusCode) +} + +func TestHandler_GetIndex_PrivateForOthers(t *testing.T) { + t.Parallel() + m := mocks.NewIndexRepo(t) + m.EXPECT().Get(mock.Anything, uint32(7)).Return( + model.Index{ID: 7, CreatorID: 6, Privacy: model.IndexPrivacyPrivate}, + nil, + ) + + app := test.GetWebApp(t, test.Mock{IndexRepo: m}) + + resp := htest.New(t, app).Get("/v0/indices/7") + + require.Equal(t, http.StatusNotFound, resp.StatusCode) +} + +func TestHandler_GetIndex_Deleted(t *testing.T) { + t.Parallel() + m := mocks.NewIndexRepo(t) + m.EXPECT().Get(mock.Anything, uint32(7)).Return( + model.Index{ID: 7, CreatorID: 6, Privacy: model.IndexPrivacyDeleted}, + nil, + ) + + app := test.GetWebApp(t, test.Mock{IndexRepo: m}) + + resp := htest.New(t, app).Get("/v0/indices/7") + + require.Equal(t, http.StatusNotFound, resp.StatusCode) +} + func TestHandler_NewIndex_NoPermission(t *testing.T) { t.Parallel() diff --git a/web/handler/index/subject.go b/web/handler/index/subject.go index c8d21270b..9db84b0c3 100644 --- a/web/handler/index/subject.go +++ b/web/handler/index/subject.go @@ -18,7 +18,7 @@ import ( "errors" "net/http" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/trim21/errgo" "github.com/bangumi/server/domain/gerr" @@ -26,15 +26,16 @@ import ( "github.com/bangumi/server/web/res" ) -func (h Handler) AddIndexSubject(c echo.Context) error { +func (h Handler) AddIndexSubject(c *echo.Context) error { var reqData req.IndexAddSubject if err := c.Echo().JSONSerializer.Deserialize(c, &reqData); err != nil { return res.JSONError(c, err) } + return h.addOrUpdateIndexSubject(c, reqData) } -func (h Handler) UpdateIndexSubject(c echo.Context) error { +func (h Handler) UpdateIndexSubject(c *echo.Context) error { var reqData req.IndexSubjectInfo if err := c.Echo().JSONSerializer.Deserialize(c, &reqData); err != nil { return res.JSONError(c, err) @@ -45,11 +46,11 @@ func (h Handler) UpdateIndexSubject(c echo.Context) error { } return h.addOrUpdateIndexSubject(c, req.IndexAddSubject{ SubjectID: subjectID, - IndexSubjectInfo: &reqData, + IndexSubjectInfo: reqData, }) } -func (h Handler) addOrUpdateIndexSubject(c echo.Context, payload req.IndexAddSubject) error { +func (h Handler) addOrUpdateIndexSubject(c *echo.Context, payload req.IndexAddSubject) error { if err := h.ensureValidStrings(payload.Comment); err != nil { return err } @@ -72,7 +73,7 @@ func (h Handler) addOrUpdateIndexSubject(c echo.Context, payload req.IndexAddSub return c.JSON(http.StatusOK, indexSubjectToResp(*indexSubject)) } -func (h Handler) RemoveIndexSubject(c echo.Context) error { +func (h Handler) RemoveIndexSubject(c *echo.Context) error { indexID, err := req.ParseID(c.Param("id")) if err != nil { return err diff --git a/web/handler/index/subject_test.go b/web/handler/index/subject_test.go index 3e360387a..795a349b2 100644 --- a/web/handler/index/subject_test.go +++ b/web/handler/index/subject_test.go @@ -18,7 +18,7 @@ import ( "net/http" "testing" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/trim21/htest" diff --git a/web/handler/index/util.go b/web/handler/index/util.go index 3e63adb43..1c75a310f 100644 --- a/web/handler/index/util.go +++ b/web/handler/index/util.go @@ -17,11 +17,12 @@ package index import ( "context" + wiki "github.com/bangumi/wiki-parser-go" + "github.com/bangumi/server/internal/index" "github.com/bangumi/server/internal/pkg/compat" "github.com/bangumi/server/internal/pkg/dam" "github.com/bangumi/server/internal/pkg/null" - "github.com/bangumi/server/pkg/wiki" "github.com/bangumi/server/web/internal/cachekey" "github.com/bangumi/server/web/res" ) diff --git a/web/handler/notification/notification.go b/web/handler/notification/notification.go deleted file mode 100644 index 043f66c2e..000000000 --- a/web/handler/notification/notification.go +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package notification - -import ( - "go.uber.org/zap" - - "github.com/bangumi/server/ctrl" - "github.com/bangumi/server/internal/notification" - "github.com/bangumi/server/web/handler/common" -) - -type Notification struct { - ctrl ctrl.Ctrl - common.Common - notificationRepo notification.Repo - log *zap.Logger -} - -func New( - common common.Common, - notificationRepo notification.Repo, - ctrl ctrl.Ctrl, - log *zap.Logger, -) (Notification, error) { - return Notification{ - Common: common, - ctrl: ctrl, - notificationRepo: notificationRepo, - log: log.Named("handler.Notification"), - }, nil -} diff --git a/web/handler/notification/notification_test.go b/web/handler/notification/notification_test.go deleted file mode 100644 index c261926d9..000000000 --- a/web/handler/notification/notification_test.go +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package notification_test - -import ( - "encoding/json" - "net/http" - "testing" - - "github.com/labstack/echo/v4" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "github.com/trim21/htest" - - "github.com/bangumi/server/internal/auth" - "github.com/bangumi/server/internal/mocks" - "github.com/bangumi/server/internal/model" - "github.com/bangumi/server/internal/pkg/test" - "github.com/bangumi/server/web/session" -) - -func TestNotification_Count(t *testing.T) { - t.Parallel() - m := mocks.NewNotificationRepo(t) - m.EXPECT().Count( - mock.Anything, - model.UserID(1), - ).Return(0, nil) - - mockAuth := mocks.NewAuthService(t) - mockAuth.EXPECT().GetByID(mock.Anything, mock.Anything).Return(auth.Auth{ID: 1}, nil) - - s := mocks.NewSessionManager(t) - s.EXPECT().Get(mock.Anything, "11").Return(session.Session{UserID: 1}, nil) - - app := test.GetWebApp(t, test.Mock{NotificationRepo: m, AuthService: mockAuth, SessionManager: s}) - - resp := htest.New(t, app). - Header(echo.HeaderCookie, "chiiNextSessionID=11"). - Get("/p/notifications/count"). - ExpectCode(http.StatusOK) - - var v int - err := json.Unmarshal(resp.Body, &v) - require.NoError(t, err) -} diff --git a/web/handler/person/collect.go b/web/handler/person/collect.go new file mode 100644 index 000000000..a060c673b --- /dev/null +++ b/web/handler/person/collect.go @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +package person + +import ( + "errors" + + "github.com/labstack/echo/v5" + + "github.com/bangumi/server/domain/gerr" + "github.com/bangumi/server/internal/collections/domain/collection" + "github.com/bangumi/server/web/accessor" + "github.com/bangumi/server/web/req" + "github.com/bangumi/server/web/res" +) + +func (h Person) CollectPerson(c *echo.Context) error { + pid, err := req.ParseID(c.Param("id")) + if err != nil { + return err + } + + uid := accessor.GetFromCtx(c).ID + return h.collectPerson(c, pid, uid) +} + +func (h Person) UncollectPerson(c *echo.Context) error { + pid, err := req.ParseID(c.Param("id")) + if err != nil { + return err + } + + uid := accessor.GetFromCtx(c).ID + return h.uncollectPerson(c, pid, uid) +} + +func (h Person) collectPerson(c *echo.Context, pid uint32, uid uint32) error { + ctx := c.Request().Context() + // check if the person exists + if _, err := h.person.Get(ctx, pid); err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.ErrNotFound + } + return res.InternalError(c, err, "get person error") + } + // check if the user has collected the person + if _, err := h.collect.GetPersonCollection(ctx, uid, collection.PersonCollectCategoryPerson, pid); err == nil { + return nil // already collected + } else if !errors.Is(err, gerr.ErrNotFound) { + return res.InternalError(c, err, "get person collect error") + } + // add the collect + if err := h.collect.AddPersonCollection(ctx, uid, collection.PersonCollectCategoryPerson, pid); err != nil { + return res.InternalError(c, err, "add person collect failed") + } + return nil +} + +func (h Person) uncollectPerson(c *echo.Context, pid uint32, uid uint32) error { + ctx := c.Request().Context() + // check if the person exists + if _, err := h.person.Get(ctx, pid); err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.ErrNotFound + } + return res.InternalError(c, err, "get person error") + } + // check if the user has collected the person + if _, err := h.collect.GetPersonCollection(ctx, uid, collection.PersonCollectCategoryPerson, pid); err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.NotFound("person not collected") + } + return res.InternalError(c, err, "get person collect error") + } + // remove the collect + if err := h.collect.RemovePersonCollection(ctx, uid, collection.PersonCollectCategoryPerson, pid); err != nil { + return res.InternalError(c, err, "remove person collect failed") + } + return nil +} diff --git a/web/handler/person/get.go b/web/handler/person/get.go index 8a4c7bda0..975ce10e5 100644 --- a/web/handler/person/get.go +++ b/web/handler/person/get.go @@ -18,8 +18,9 @@ import ( "errors" "net/http" "strconv" + "time" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/trim21/errgo" "github.com/bangumi/server/domain/gerr" @@ -27,7 +28,7 @@ import ( "github.com/bangumi/server/web/res" ) -func (h Person) Get(c echo.Context) error { +func (h Person) Get(c *echo.Context) error { id, err := req.ParseID(c.Param("id")) if err != nil { return err @@ -42,6 +43,8 @@ func (h Person) Get(c echo.Context) error { return errgo.Wrap(err, "failed to get person") } + res.SetCacheControl(c, res.CacheControlParams{Public: true, MaxAge: time.Hour}) + if r.Redirect != 0 { return c.Redirect(http.StatusFound, "/v0/persons/"+strconv.FormatUint(uint64(r.Redirect), 10)) } @@ -49,7 +52,7 @@ func (h Person) Get(c echo.Context) error { return c.JSON(http.StatusOK, res.ConvertModelPerson(r)) } -func (h Person) GetImage(c echo.Context) error { +func (h Person) GetImage(c *echo.Context) error { id, err := req.ParseID(c.Param("id")) if err != nil { return err @@ -69,6 +72,8 @@ func (h Person) GetImage(c echo.Context) error { return res.BadRequest("bad image type: " + c.QueryParam("type")) } + res.SetCacheControl(c, res.CacheControlParams{Public: true, MaxAge: time.Hour * 24}) + if l == "" { return c.Redirect(http.StatusFound, res.DefaultImageURL) } diff --git a/web/handler/person/get_related_characters.go b/web/handler/person/get_related_characters.go index 098a38b96..3da24f61f 100644 --- a/web/handler/person/get_related_characters.go +++ b/web/handler/person/get_related_characters.go @@ -18,8 +18,9 @@ import ( "context" "errors" "net/http" + "time" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/trim21/errgo" "github.com/bangumi/server/domain/gerr" @@ -30,7 +31,8 @@ import ( "github.com/bangumi/server/web/res" ) -func (h Person) GetRelatedCharacters(c echo.Context) error { +//nolint:funlen +func (h Person) GetRelatedCharacters(c *echo.Context) error { id, err := req.ParseID(c.Param("id")) if err != nil { return err @@ -39,12 +41,13 @@ func (h Person) GetRelatedCharacters(c echo.Context) error { r, err := h.person.Get(c.Request().Context(), id) if err != nil { if errors.Is(err, gerr.ErrNotFound) { + res.SetCacheControl(c, res.CacheControlParams{Public: true, MaxAge: time.Hour}) return res.ErrNotFound } return errgo.Wrap(err, "failed to get person") } - if r.Redirect != 0 { + res.SetCacheControl(c, res.CacheControlParams{Public: true, MaxAge: time.Hour}) return res.ErrNotFound } @@ -60,7 +63,7 @@ func (h Person) GetRelatedCharacters(c echo.Context) error { SubjectID: relation.Subject.ID, } } - subjectRelations, err := h.c.GetSubjectRelationByIDs(c.Request().Context(), compositeIDs) + subjectRelations, err := h.character.GetSubjectRelationByIDs(c.Request().Context(), compositeIDs) if err != nil { return errgo.Wrap(err, "CharacterRepo.GetRelations") } @@ -84,19 +87,21 @@ func (h Person) GetRelatedCharacters(c echo.Context) error { Type: rel.Character.Type, Images: res.PersonImage(rel.Character.Image), SubjectID: rel.Subject.ID, + SubjectType: rel.Subject.TypeID, SubjectName: rel.Subject.Name, SubjectNameCn: rel.Subject.NameCN, Staff: res.CharacterStaffString(subjectTypeID), } } + res.SetCacheControl(c, res.CacheControlParams{Public: true, MaxAge: time.Hour}) return c.JSON(http.StatusOK, response) } func (h Person) getPersonRelatedCharacters( ctx context.Context, personID model.PersonID, ) ([]model.PersonCharacterRelation, error) { - relations, err := h.c.GetPersonRelated(ctx, personID) + relations, err := h.character.GetPersonRelated(ctx, personID) if err != nil { return nil, errgo.Wrap(err, "CharacterRepo.GetPersonRelated") } @@ -112,7 +117,7 @@ func (h Person) getPersonRelatedCharacters( subjectIDs[i] = relation.SubjectID } - characters, err := h.c.GetByIDs(ctx, characterIDs) + characters, err := h.character.GetByIDs(ctx, characterIDs) if err != nil { return nil, errgo.Wrap(err, "CharacterRepo.GetByIDs") } diff --git a/web/handler/person/get_related_subjects.go b/web/handler/person/get_related_subjects.go index 062002c80..e7ce0d95c 100644 --- a/web/handler/person/get_related_subjects.go +++ b/web/handler/person/get_related_subjects.go @@ -18,8 +18,9 @@ import ( "context" "errors" "net/http" + "time" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/trim21/errgo" "github.com/bangumi/server/domain" @@ -32,7 +33,7 @@ import ( "github.com/bangumi/server/web/res" ) -func (h Person) GetRelatedSubjects(c echo.Context) error { +func (h Person) GetRelatedSubjects(c *echo.Context) error { id, err := req.ParseID(c.Param("id")) if err != nil { return err @@ -60,6 +61,8 @@ func (h Person) GetRelatedSubjects(c echo.Context) error { for i, relation := range relations { response[i] = res.PersonRelatedSubject{ SubjectID: relation.Subject.ID, + Eps: relation.Eps, + Type: relation.Subject.TypeID, Staff: vars.StaffMap[relation.Subject.TypeID][relation.TypeID].String(), Name: relation.Subject.Name, NameCn: relation.Subject.NameCN, @@ -67,6 +70,8 @@ func (h Person) GetRelatedSubjects(c echo.Context) error { } } + res.SetCacheControl(c, res.CacheControlParams{Public: true, MaxAge: time.Hour}) + return c.JSON(http.StatusOK, response) } @@ -100,6 +105,7 @@ func (h Person) getPersonRelated( Person: person, Subject: s, TypeID: rel.TypeID, + Eps: rel.Eps, }) } diff --git a/web/handler/person/person.go b/web/handler/person/person.go index 3146859ae..a087c9cef 100644 --- a/web/handler/person/person.go +++ b/web/handler/person/person.go @@ -17,27 +17,31 @@ package person import ( "github.com/bangumi/server/ctrl" "github.com/bangumi/server/internal/character" + "github.com/bangumi/server/internal/collections" "github.com/bangumi/server/internal/person" "github.com/bangumi/server/internal/subject" ) type Person struct { - person person.Repo - c character.Repo - ctrl ctrl.Ctrl - subject subject.Repo + ctrl ctrl.Ctrl + person person.Repo + character character.Repo + subject subject.Repo + collect collections.Repo } func New( ctrl ctrl.Ctrl, person person.Repo, subject subject.Repo, - c character.Repo, + character character.Repo, + collect collections.Repo, ) (Person, error) { return Person{ - person: person, - ctrl: ctrl, - c: c, - subject: subject, + ctrl: ctrl, + person: person, + character: character, + subject: subject, + collect: collect, }, nil } diff --git a/web/handler/person/person_test.go b/web/handler/person/person_test.go index 64dd45f24..073e5b00f 100644 --- a/web/handler/person/person_test.go +++ b/web/handler/person/person_test.go @@ -61,7 +61,6 @@ func TestPerson_GetImage(t *testing.T) { app := test.GetWebApp(t, test.Mock{PersonRepo: m}) for _, imageType := range []string{"small", "grid", "large", "medium"} { - imageType := imageType t.Run(imageType, func(t *testing.T) { t.Parallel() diff --git a/web/handler/pm/count_types.go b/web/handler/pm/count_types.go deleted file mode 100644 index 6b8fba32e..000000000 --- a/web/handler/pm/count_types.go +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package pm - -import ( - "net/http" - - "github.com/labstack/echo/v4" - - "github.com/bangumi/server/web/accessor" - "github.com/bangumi/server/web/res" -) - -func (h PrivateMessage) CountTypes(c echo.Context) error { - accessor := accessor.GetFromCtx(c) - counts, err := h.pmRepo.CountTypes(c.Request().Context(), accessor.ID) - if err != nil { - return res.InternalError(c, err, "failed to count private message types") - } - return c.JSON(http.StatusOK, res.PrivateMessageTypeCounts{ - Unread: counts.Unread, - Inbox: counts.Inbox, - Outbox: counts.Outbox, - }) -} diff --git a/web/handler/pm/create.go b/web/handler/pm/create.go deleted file mode 100644 index b6d5261ea..000000000 --- a/web/handler/pm/create.go +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package pm - -import ( - "errors" - "net/http" - - "github.com/labstack/echo/v4" - "github.com/samber/lo" - - "github.com/bangumi/server/ctrl" - "github.com/bangumi/server/internal/model" - "github.com/bangumi/server/internal/pkg/generic/slice" - "github.com/bangumi/server/internal/pkg/null" - "github.com/bangumi/server/internal/pm" - "github.com/bangumi/server/web/accessor" - "github.com/bangumi/server/web/req" - "github.com/bangumi/server/web/res" -) - -func (h PrivateMessage) Create(c echo.Context) error { - accessor := accessor.GetFromCtx(c) - var r req.PrivateMessageCreate - if err := c.Echo().JSONSerializer.Deserialize(c, &r); err != nil { - return res.JSONError(c, err) - } - - if err := h.Common.V.Struct(r); err != nil { - return h.ValidationError(c, err) - } - receiverIDs := slice.Map(r.ReceiverIDs, func(v uint32) model.UserID { return v }) - - msgs, err := h.ctrl.CreatePrivateMessage( - c.Request().Context(), - accessor.ID, - receiverIDs, - pm.IDFilter{Type: null.NewFromPtr(r.RelatedID)}, - r.Title, - r.Content) - if err != nil { - switch { - case errors.Is(err, ctrl.ErrPmBlocked): - case errors.Is(err, ctrl.ErrPmNotAFriend): - case errors.Is(err, ctrl.ErrPmNotAllReceiversExist): - case errors.Is(err, ctrl.ErrPmReceiverReject): - case errors.Is(err, pm.ErrPmRelatedNotExists): - case errors.Is(err, pm.ErrPmInvalidOperation): - return res.BadRequest(err.Error()) - } - return res.InternalError(c, err, "failed to create private message(s)") - } - userIDs := make([]model.UserID, len(r.ReceiverIDs)+1) - copy(userIDs, receiverIDs) - userIDs[len(userIDs)-1] = accessor.ID - users, err := h.u.GetByIDs(c.Request().Context(), lo.Uniq(userIDs)) - if err != nil { - return res.InternalError(c, err, "failed to get users") - } - return c.JSON(http.StatusOK, slice.Map(msgs, func(v pm.PrivateMessage) res.PrivateMessage { - return res.ConvertModelPrivateMessage(v, users) - })) -} diff --git a/web/handler/pm/delete.go b/web/handler/pm/delete.go deleted file mode 100644 index 8c6c10944..000000000 --- a/web/handler/pm/delete.go +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package pm - -import ( - "errors" - "net/http" - - "github.com/labstack/echo/v4" - - "github.com/bangumi/server/internal/model" - "github.com/bangumi/server/internal/pkg/generic/slice" - "github.com/bangumi/server/internal/pm" - "github.com/bangumi/server/web/accessor" - "github.com/bangumi/server/web/req" - "github.com/bangumi/server/web/res" -) - -func (h PrivateMessage) Delete(c echo.Context) error { - accessor := accessor.GetFromCtx(c) - var r req.PrivateMessageDelete - if err := c.Echo().JSONSerializer.Deserialize(c, &r); err != nil { - return res.JSONError(c, err) - } - - if err := h.Common.V.Struct(r); err != nil { - return h.ValidationError(c, err) - } - err := h.pmRepo. - Delete( - c.Request().Context(), - accessor.ID, - slice.Map(r.IDs, func(v uint32) model.PrivateMessageID { - return v - })) - if err != nil { - if errors.Is(err, pm.ErrPmUserIrrelevant) { - return res.BadRequest(err.Error()) - } - return res.InternalError(c, err, "failed to delete private message(s)") - } - return c.NoContent(http.StatusNoContent) -} diff --git a/web/handler/pm/list.go b/web/handler/pm/list.go deleted file mode 100644 index 3d81beac4..000000000 --- a/web/handler/pm/list.go +++ /dev/null @@ -1,81 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package pm - -import ( - "net/http" - - "github.com/labstack/echo/v4" - "github.com/samber/lo" - - "github.com/bangumi/server/internal/model" - "github.com/bangumi/server/internal/pkg/generic/slice" - "github.com/bangumi/server/internal/pm" - "github.com/bangumi/server/web/accessor" - "github.com/bangumi/server/web/req" - "github.com/bangumi/server/web/res" -) - -func (h PrivateMessage) List(c echo.Context) error { - accessor := accessor.GetFromCtx(c) - folder, err := req.ParsePrivateMessageFolder(c.QueryParam("folder")) - if err != nil { - return err - } - page, err := req.GetPageQuery(c, req.DefaultPageLimit, req.DefaultMaxPageLimit) - if err != nil { - return err - } - ctx := c.Request().Context() - count, err := h.pmRepo.CountByFolder(ctx, accessor.ID, folder) - if err != nil { - return res.InternalError(c, err, "failed to count private messages") - } - list, err := h.pmRepo.List(ctx, accessor.ID, folder, page.Offset, page.Limit) - if err != nil { - return res.InternalError(c, err, "failed to list private messages") - } - if len(list) == 0 { - return c.JSON(http.StatusOK, res.Paged{ - Data: make([]res.PrivateMessage, 0), - Total: count, - Limit: page.Limit, - Offset: page.Offset, - }) - } - userIDs := make([]model.UserID, len(list)+1) - for i := range list { - if folder == pm.FolderTypeInbox { - userIDs[i] = list[i].Self.SenderID - } else { - userIDs[i] = list[i].Self.ReceiverID - } - } - userIDs[len(userIDs)-1] = accessor.ID - userIDs = lo.Uniq(userIDs) - users, err := h.u.GetByIDs(c.Request().Context(), userIDs) - if err != nil { - return res.InternalError(c, err, "failed to get users") - } - data := slice.Map(list, func(v pm.PrivateMessageListItem) res.PrivateMessage { - return res.ConvertModelPrivateMessageListItem(v, users) - }) - return c.JSON(http.StatusOK, res.Paged{ - Data: data, - Total: count, - Limit: page.Limit, - Offset: page.Offset, - }) -} diff --git a/web/handler/pm/list_recent_contact.go b/web/handler/pm/list_recent_contact.go deleted file mode 100644 index 07f88db30..000000000 --- a/web/handler/pm/list_recent_contact.go +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package pm - -import ( - "net/http" - - "github.com/labstack/echo/v4" - - "github.com/bangumi/server/internal/model" - "github.com/bangumi/server/internal/pkg/generic/slice" - "github.com/bangumi/server/web/accessor" - "github.com/bangumi/server/web/res" -) - -func (h PrivateMessage) ListRecentContact(c echo.Context) error { - accessor := accessor.GetFromCtx(c) - contactIDs, err := h.pmRepo.ListRecentContact(c.Request().Context(), accessor.ID) - if err != nil { - return res.InternalError(c, err, "failed to list recent contact") - } - contacts, err := h.u.GetByIDs(c.Request().Context(), contactIDs) - if err != nil { - return res.InternalError(c, err, "failed to get contacts") - } - return c.JSON(http.StatusOK, slice.MapFilter(contactIDs, func(v model.UserID) (res.User, bool) { - if m, ok := contacts[v]; ok { - return res.ConvertModelUser(m), ok - } - return res.User{}, false - })) -} diff --git a/web/handler/pm/list_related.go b/web/handler/pm/list_related.go deleted file mode 100644 index 9e436fa2c..000000000 --- a/web/handler/pm/list_related.go +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package pm - -import ( - "errors" - "net/http" - - "github.com/labstack/echo/v4" - - "github.com/bangumi/server/domain/gerr" - "github.com/bangumi/server/internal/model" - "github.com/bangumi/server/internal/pkg/generic/slice" - "github.com/bangumi/server/internal/pm" - "github.com/bangumi/server/web/accessor" - "github.com/bangumi/server/web/req" - "github.com/bangumi/server/web/res" -) - -func (h PrivateMessage) ListRelated(c echo.Context) error { - accessor := accessor.GetFromCtx(c) - relatedID, err := req.ParseID(c.Param("id")) - if err != nil { - return err - } - list, err := h.pmRepo.ListRelated(c.Request().Context(), accessor.ID, relatedID) - if err != nil { - switch { - case errors.Is(err, gerr.ErrNotFound): - return res.ErrNotFound - case errors.Is(err, pm.ErrPmDeleted): - case errors.Is(err, pm.ErrPmNotOwned): - return res.BadRequest(err.Error()) - } - return res.InternalError(c, err, "failed to list related private messages") - } - userIDs := []model.UserID{list[0].SenderID, list[0].ReceiverID} - users, err := h.u.GetByIDs(c.Request().Context(), userIDs) - if err != nil { - return res.InternalError(c, err, "failed to get users") - } - data := slice.Map(list, func(v pm.PrivateMessage) res.PrivateMessage { - return res.ConvertModelPrivateMessage(v, users) - }) - return c.JSON(http.StatusOK, data) -} diff --git a/web/handler/pm/mark_read.go b/web/handler/pm/mark_read.go deleted file mode 100644 index 0386ef4ca..000000000 --- a/web/handler/pm/mark_read.go +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package pm - -import ( - "errors" - "net/http" - - "github.com/labstack/echo/v4" - - "github.com/bangumi/server/internal/pm" - "github.com/bangumi/server/web/accessor" - "github.com/bangumi/server/web/req" - "github.com/bangumi/server/web/res" -) - -func (h PrivateMessage) MarkRead(c echo.Context) error { - accessor := accessor.GetFromCtx(c) - var r req.PrivateMessageMarkRead - if err := c.Echo().JSONSerializer.Deserialize(c, &r); err != nil { - return res.JSONError(c, err) - } - - if err := h.Common.V.Struct(r); err != nil { - return h.ValidationError(c, err) - } - err := h.pmRepo.MarkRead(c.Request().Context(), accessor.ID, r.ID) - if err != nil { - if errors.Is(err, pm.ErrPmInvalidOperation) { - return res.BadRequest(err.Error()) - } - return res.InternalError(c, err, "failed to mark private message(s) read") - } - return c.NoContent(http.StatusNoContent) -} diff --git a/web/handler/pm/pm.go b/web/handler/pm/pm.go deleted file mode 100644 index 9fbbe71ee..000000000 --- a/web/handler/pm/pm.go +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package pm - -import ( - "go.uber.org/zap" - - "github.com/bangumi/server/ctrl" - "github.com/bangumi/server/internal/pm" - "github.com/bangumi/server/internal/user" - "github.com/bangumi/server/web/handler/common" -) - -type PrivateMessage struct { - ctrl ctrl.Ctrl - common.Common - pmRepo pm.Repo - u user.Repo - log *zap.Logger -} - -func New( - common common.Common, - pmRepo pm.Repo, - ctrl ctrl.Ctrl, - user user.Repo, - log *zap.Logger, -) (PrivateMessage, error) { - return PrivateMessage{ - Common: common, - ctrl: ctrl, - u: user, - pmRepo: pmRepo, - log: log.Named("handler.PrivateMessage"), - }, nil -} diff --git a/web/handler/pm/pm_test.go b/web/handler/pm/pm_test.go deleted file mode 100644 index 3f725a101..000000000 --- a/web/handler/pm/pm_test.go +++ /dev/null @@ -1,215 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package pm_test - -import ( - "net/http" - "testing" - - "github.com/labstack/echo/v4" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "github.com/trim21/htest" - - "github.com/bangumi/server/domain/gerr" - "github.com/bangumi/server/internal/auth" - "github.com/bangumi/server/internal/mocks" - "github.com/bangumi/server/internal/model" - "github.com/bangumi/server/internal/pkg/null" - "github.com/bangumi/server/internal/pkg/test" - "github.com/bangumi/server/internal/pm" - "github.com/bangumi/server/web/req" - "github.com/bangumi/server/web/session" -) - -func TestPrivateMessage_List(t *testing.T) { - t.Parallel() - m := mocks.NewPrivateMessageRepo(t) - m.EXPECT().CountByFolder(mock.Anything, model.UserID(1), pm.FolderTypeInbox).Return(1, nil) - m.EXPECT().List( - mock.Anything, - model.UserID(1), - pm.FolderTypeInbox, - 0, - 10, - ).Return([]pm.PrivateMessageListItem{ - { - Main: pm.PrivateMessage{}, - Self: pm.PrivateMessage{}, - }, - }, nil) - - mockAuth := mocks.NewAuthService(t) - mockAuth.EXPECT().GetByID(mock.Anything, mock.Anything).Return(auth.Auth{ID: 1}, nil) - - s := mocks.NewSessionManager(t) - s.EXPECT().Get(mock.Anything, "11").Return(session.Session{UserID: 1}, nil) - - app := test.GetWebApp(t, test.Mock{PrivateMessageRepo: m, AuthService: mockAuth, SessionManager: s}) - - resp := htest.New(t, app). - Header(echo.HeaderCookie, "chiiNextSessionID=11"). - Get("/p/pms/list?offset=0&limit=10&folder=inbox") - - require.Equal(t, http.StatusOK, resp.StatusCode) -} - -func TestPrivateMessage_ListRelated(t *testing.T) { - t.Parallel() - m := mocks.NewPrivateMessageRepo(t) - m.EXPECT().ListRelated( - mock.Anything, - model.UserID(1), - model.PrivateMessageID(1), - ).Return([]pm.PrivateMessage{}, gerr.ErrNotFound) - - mockAuth := mocks.NewAuthService(t) - mockAuth.EXPECT().GetByID(mock.Anything, mock.Anything).Return(auth.Auth{ID: 1}, nil) - - s := mocks.NewSessionManager(t) - s.EXPECT().Get(mock.Anything, "11").Return(session.Session{UserID: 1}, nil) - - app := test.GetWebApp(t, test.Mock{PrivateMessageRepo: m, AuthService: mockAuth, SessionManager: s}) - - resp := htest.New(t, app). - Header(echo.HeaderCookie, "chiiNextSessionID=11"). - Get("/p/pms/related-msgs/1") - - require.Equal(t, http.StatusNotFound, resp.StatusCode) -} - -func TestPrivateMessage_ListRecentContact(t *testing.T) { - t.Parallel() - m := mocks.NewPrivateMessageRepo(t) - m.EXPECT().ListRecentContact( - mock.Anything, - model.UserID(1), - ).Return([]model.UserID{}, nil) - - mockAuth := mocks.NewAuthService(t) - mockAuth.EXPECT().GetByID(mock.Anything, mock.Anything).Return(auth.Auth{ID: 1}, nil) - - s := mocks.NewSessionManager(t) - s.EXPECT().Get(mock.Anything, "11").Return(session.Session{UserID: 1}, nil) - - app := test.GetWebApp(t, test.Mock{PrivateMessageRepo: m, AuthService: mockAuth, SessionManager: s}) - - resp := htest.New(t, app). - Header(echo.HeaderCookie, "chiiNextSessionID=11"). - Get("/p/pms/contacts/recent") - - require.Equal(t, http.StatusOK, resp.StatusCode) -} - -func TestPrivateMessage_CountTypes(t *testing.T) { - t.Parallel() - m := mocks.NewPrivateMessageRepo(t) - m.EXPECT().CountTypes( - mock.Anything, - model.UserID(1), - ).Return(pm.PrivateMessageTypeCounts{}, nil) - - mockAuth := mocks.NewAuthService(t) - mockAuth.EXPECT().GetByID(mock.Anything, mock.Anything).Return(auth.Auth{ID: 1}, nil) - - s := mocks.NewSessionManager(t) - s.EXPECT().Get(mock.Anything, "111").Return(session.Session{UserID: 1}, nil) - - app := test.GetWebApp(t, test.Mock{PrivateMessageRepo: m, AuthService: mockAuth, SessionManager: s}) - - resp := htest.New(t, app). - Header(echo.HeaderCookie, "chiiNextSessionID=111"). - Get("/p/pms/counts") - - require.Equal(t, http.StatusOK, resp.StatusCode) -} - -func TestPrivateMessage_MarkRead(t *testing.T) { - t.Parallel() - m := mocks.NewPrivateMessageRepo(t) - m.EXPECT().MarkRead( - mock.Anything, - model.UserID(1), - model.PrivateMessageID(1), - ).Return(nil) - - mockAuth := mocks.NewAuthService(t) - mockAuth.EXPECT().GetByID(mock.Anything, mock.Anything).Return(auth.Auth{ID: 1}, nil) - - s := mocks.NewSessionManager(t) - s.EXPECT().Get(mock.Anything, "11").Return(session.Session{UserID: 1}, nil) - - app := test.GetWebApp(t, test.Mock{PrivateMessageRepo: m, AuthService: mockAuth, SessionManager: s}) - - resp := htest.New(t, app). - Header(echo.HeaderCookie, "chiiNextSessionID=11"). - BodyJSON(req.PrivateMessageMarkRead{ID: 1}). - Patch("/p/pms/read") - - require.Equal(t, http.StatusNoContent, resp.StatusCode) -} - -func TestPrivateMessage_Create(t *testing.T) { - t.Parallel() - m := mocks.NewPrivateMessageRepo(t) - m.EXPECT().Create( - mock.Anything, - model.UserID(1), - []model.UserID{382951}, - pm.IDFilter{Type: null.NewFromPtr[model.PrivateMessageID](nil)}, - "测试标题", - "测试内容", - ).Return([]pm.PrivateMessage{}, nil) - - mockAuth := mocks.NewAuthService(t) - mockAuth.EXPECT().GetByID(mock.Anything, mock.Anything).Return(auth.Auth{ID: 1}, nil) - - s := mocks.NewSessionManager(t) - s.EXPECT().Get(mock.Anything, "111").Return(session.Session{UserID: 1}, nil) - - app := test.GetWebApp(t, test.Mock{PrivateMessageRepo: m, AuthService: mockAuth, SessionManager: s}) - - resp := htest.New(t, app). - Header(echo.HeaderCookie, "chiiNextSessionID=111"). - BodyJSON(req.PrivateMessageCreate{Title: "测试标题", Content: "测试内容", ReceiverIDs: []uint32{382951}}). - Post("/p/pms") - - require.Equal(t, http.StatusOK, resp.StatusCode) -} - -func TestPrivateMessage_Delete(t *testing.T) { - t.Parallel() - m := mocks.NewPrivateMessageRepo(t) - m.EXPECT().Delete( - mock.Anything, - model.UserID(1), - []model.PrivateMessageID{1}, - ).Return(nil) - - mockAuth := mocks.NewAuthService(t) - mockAuth.EXPECT().GetByID(mock.Anything, mock.Anything).Return(auth.Auth{ID: 1}, nil) - - s := mocks.NewSessionManager(t) - s.EXPECT().Get(mock.Anything, "111").Return(session.Session{UserID: 1}, nil) - - app := test.GetWebApp(t, test.Mock{PrivateMessageRepo: m, AuthService: mockAuth, SessionManager: s}) - - resp := htest.New(t, app). - Header(echo.HeaderCookie, "chiiNextSessionID=111"). - BodyJSON(req.PrivateMessageDelete{IDs: []uint32{1}}). - Delete("/p/pms") - - require.Equal(t, http.StatusNoContent, resp.StatusCode) -} diff --git a/web/handler/revision.go b/web/handler/revision.go index 33e61aca5..df4d85330 100644 --- a/web/handler/revision.go +++ b/web/handler/revision.go @@ -20,7 +20,7 @@ import ( "net/http" "strconv" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/samber/lo" "github.com/trim21/errgo" @@ -32,7 +32,7 @@ import ( "github.com/bangumi/server/web/res" ) -func (h Handler) ListPersonRevision(c echo.Context) error { +func (h Handler) ListPersonRevision(c *echo.Context) error { page, err := req.GetPageQuery(c, req.DefaultPageLimit, req.DefaultMaxPageLimit) if err != nil { return err @@ -45,7 +45,7 @@ func (h Handler) ListPersonRevision(c echo.Context) error { return h.listPersonRevision(c, personID, page) } -func (h Handler) listPersonRevision(c echo.Context, personID model.PersonID, page req.PageQuery) error { +func (h Handler) listPersonRevision(c *echo.Context, personID model.PersonID, page req.PageQuery) error { var response = res.Paged{ Limit: page.Limit, Offset: page.Offset, @@ -91,7 +91,7 @@ func (h Handler) listPersonRevision(c echo.Context, personID model.PersonID, pag return c.JSON(http.StatusOK, response) } -func (h Handler) GetPersonRevision(c echo.Context) error { +func (h Handler) GetPersonRevision(c *echo.Context) error { id, err := gstr.ParseUint32(c.Param("id")) if err != nil || id <= 0 { return res.BadRequest(fmt.Sprintf("bad param id: %s", c.Param("id"))) @@ -113,7 +113,7 @@ func (h Handler) GetPersonRevision(c echo.Context) error { return c.JSON(http.StatusOK, convertModelPersonRevision(&r, creatorMap)) } -func (h Handler) ListCharacterRevision(c echo.Context) error { +func (h Handler) ListCharacterRevision(c *echo.Context) error { page, err := req.GetPageQuery(c, req.DefaultPageLimit, req.DefaultMaxPageLimit) if err != nil { return err @@ -127,7 +127,7 @@ func (h Handler) ListCharacterRevision(c echo.Context) error { return h.listCharacterRevision(c, characterID, page) } -func (h Handler) listCharacterRevision(c echo.Context, characterID model.CharacterID, page req.PageQuery) error { +func (h Handler) listCharacterRevision(c *echo.Context, characterID model.CharacterID, page req.PageQuery) error { var response = res.Paged{ Limit: page.Limit, Offset: page.Offset, @@ -172,7 +172,7 @@ func (h Handler) listCharacterRevision(c echo.Context, characterID model.Charact return c.JSON(http.StatusOK, response) } -func (h Handler) GetCharacterRevision(c echo.Context) error { +func (h Handler) GetCharacterRevision(c *echo.Context) error { id, err := gstr.ParseUint32(c.Param("id")) if err != nil || id <= 0 { return res.NewError( @@ -197,7 +197,7 @@ func (h Handler) GetCharacterRevision(c echo.Context) error { return c.JSON(http.StatusOK, convertModelCharacterRevision(&r, creatorMap)) } -func (h Handler) ListSubjectRevision(c echo.Context) error { +func (h Handler) ListSubjectRevision(c *echo.Context) error { page, err := req.GetPageQuery(c, req.DefaultPageLimit, req.DefaultMaxPageLimit) if err != nil { return err @@ -210,7 +210,7 @@ func (h Handler) ListSubjectRevision(c echo.Context) error { return h.listSubjectRevision(c, subjectID, page) } -func (h Handler) listSubjectRevision(c echo.Context, subjectID model.SubjectID, page req.PageQuery) error { +func (h Handler) listSubjectRevision(c *echo.Context, subjectID model.SubjectID, page req.PageQuery) error { var response = res.Paged{ Limit: page.Limit, Offset: page.Offset, @@ -255,7 +255,7 @@ func (h Handler) listSubjectRevision(c echo.Context, subjectID model.SubjectID, return c.JSON(http.StatusOK, response) } -func (h Handler) GetSubjectRevision(c echo.Context) error { +func (h Handler) GetSubjectRevision(c *echo.Context) error { id, err := gstr.ParseUint32(c.Param("id")) if err != nil || id == 0 { return res.BadRequest("bad param id: " + strconv.Quote(c.Param("id"))) @@ -285,7 +285,7 @@ func convertModelPersonRevision(r *model.PersonRevision, creatorMap map[model.Us Summary: r.Summary, Creator: res.Creator{ Username: creator.UserName, - Nickname: creator.UserName, + Nickname: creator.NickName, }, CreatedAt: r.CreatedAt, Data: nil, @@ -340,7 +340,7 @@ func convertModelSubjectRevision( Summary: r.Summary, Creator: res.Creator{ Username: creator.UserName, - Nickname: creator.UserName, + Nickname: creator.NickName, }, CreatedAt: r.CreatedAt, Data: data, @@ -357,7 +357,7 @@ func convertModelCharacterRevision( Summary: r.Summary, Creator: res.Creator{ Username: creator.UserName, - Nickname: creator.UserName, + Nickname: creator.NickName, }, CreatedAt: r.CreatedAt, } @@ -383,7 +383,7 @@ func convertModelEpisodeRevision(r *model.EpisodeRevision, creatorMap map[model. Summary: r.Summary, Creator: res.Creator{ Username: creator.UserName, - Nickname: creator.UserName, + Nickname: creator.NickName, }, CreatedAt: r.CreatedAt, } diff --git a/web/handler/revision_episode.go b/web/handler/revision_episode.go index 95f152866..773be09af 100644 --- a/web/handler/revision_episode.go +++ b/web/handler/revision_episode.go @@ -20,7 +20,7 @@ import ( "net/http" "strconv" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/samber/lo" "github.com/trim21/errgo" @@ -31,7 +31,7 @@ import ( "github.com/bangumi/server/web/res" ) -func (h Handler) GetEpisodeRevision(c echo.Context) error { +func (h Handler) GetEpisodeRevision(c *echo.Context) error { id, err := gstr.ParseUint32(c.Param("id")) if err != nil || id <= 0 { return res.NewError( @@ -56,7 +56,7 @@ func (h Handler) GetEpisodeRevision(c echo.Context) error { return c.JSON(http.StatusOK, convertModelEpisodeRevision(&r, creatorMap)) } -func (h Handler) ListEpisodeRevision(c echo.Context) error { +func (h Handler) ListEpisodeRevision(c *echo.Context) error { page, err := req.GetPageQuery(c, req.DefaultPageLimit, req.DefaultMaxPageLimit) if err != nil { return err @@ -70,7 +70,7 @@ func (h Handler) ListEpisodeRevision(c echo.Context) error { return h.listEpisodeRevision(c, episodeID, page) } -func (h Handler) listEpisodeRevision(c echo.Context, episodeID model.EpisodeID, page req.PageQuery) error { +func (h Handler) listEpisodeRevision(c *echo.Context, episodeID model.EpisodeID, page req.PageQuery) error { var response = res.Paged{ Limit: page.Limit, Offset: page.Offset, diff --git a/web/handler/revision_test.go b/web/handler/revision_test.go index 13c54d233..88690293d 100644 --- a/web/handler/revision_test.go +++ b/web/handler/revision_test.go @@ -62,7 +62,6 @@ func TestHandler_ListPersonRevision_Bad_ID(t *testing.T) { badIDs := []string{"-1", "a", "0"} for _, id := range badIDs { - id := id t.Run(id, func(t *testing.T) { t.Parallel() @@ -122,7 +121,6 @@ func TestHandler_ListSubjectRevision_Bad_ID(t *testing.T) { badIDs := []string{"-1", "a", "0"} for _, id := range badIDs { - id := id t.Run(id, func(t *testing.T) { t.Parallel() diff --git a/web/handler/search.go b/web/handler/search.go index 38895045f..57f9dec34 100644 --- a/web/handler/search.go +++ b/web/handler/search.go @@ -15,9 +15,19 @@ package handler import ( - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" + + "github.com/bangumi/server/internal/search" ) -func (h Handler) Search(c echo.Context) error { - return h.search.Handle(c) //nolint:wrapcheck +func (h Handler) SearchSubjects(c *echo.Context) error { + return h.search.Handle(c, search.SearchTargetSubject) //nolint:wrapcheck +} + +func (h Handler) SearchCharacters(c *echo.Context) error { + return h.search.Handle(c, search.SearchTargetCharacter) //nolint:wrapcheck +} + +func (h Handler) SearchPersons(c *echo.Context) error { + return h.search.Handle(c, search.SearchTargetPerson) //nolint:wrapcheck } diff --git a/web/handler/subject/browse.go b/web/handler/subject/browse.go new file mode 100644 index 000000000..ed518544b --- /dev/null +++ b/web/handler/subject/browse.go @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +package subject + +import ( + "net/http" + + "github.com/labstack/echo/v5" + "github.com/trim21/errgo" + + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/pkg/gstr" + "github.com/bangumi/server/internal/pkg/null" + "github.com/bangumi/server/internal/subject" + "github.com/bangumi/server/web/accessor" + "github.com/bangumi/server/web/req" + "github.com/bangumi/server/web/res" +) + +func (h Subject) Browse(c *echo.Context) error { + page, err := req.GetPageQuery(c, req.DefaultPageLimit, req.DefaultMaxPageLimit) + if err != nil { + return err + } + + filter, err := parseBrowseQuery(c) + if err != nil { + return err + } + + count, err := h.subject.Count(c.Request().Context(), *filter) + if err != nil { + return errgo.Wrap(err, "failed to count subjects") + } + + if count == 0 { + return c.JSON(http.StatusOK, res.Paged{ + Data: []res.SubjectV0{}, Total: count, Limit: page.Limit, Offset: page.Offset}) + } + + if err = page.Check(count); err != nil { + return err + } + + subjects, err := h.subject.Browse(c.Request().Context(), *filter, page.Limit, page.Offset) + if err != nil { + return errgo.Wrap(err, "failed to browse subjects") + } + ids := make([]model.SubjectID, 0, len(subjects)) + for _, s := range subjects { + ids = append(ids, s.ID) + } + tags, err := h.tag.GetByIDs(c.Request().Context(), ids) + if err != nil { + return errgo.Wrap(err, "failed to get tags") + } + + data := make([]res.SubjectV0, 0, len(subjects)) + for _, s := range subjects { + metaTags := tags[s.ID] + data = append(data, res.ToSubjectV0(s, 0, metaTags)) + } + + return c.JSON(http.StatusOK, res.Paged{Data: data, Total: count, Limit: page.Limit, Offset: page.Offset}) +} + +func parseBrowseQuery(c *echo.Context) (*subject.BrowseFilter, error) { + filter := subject.BrowseFilter{} + u := accessor.GetFromCtx(c) + filter.NSFW = null.Bool{Value: false, Set: !u.AllowNSFW()} + if stype, err := req.ParseSubjectType(c.QueryParam("type")); err != nil { + return nil, res.BadRequest(err.Error()) + } else { + filter.Type = stype + } + if catStr := c.QueryParam("cat"); catStr != "" { + if cat, err := req.ParseSubjectCategory(filter.Type, catStr); err != nil { + return nil, res.BadRequest(err.Error()) + } else { + filter.Category = null.Uint16{Value: cat, Set: true} + } + } + if filter.Type == model.SubjectTypeBook { + if seriesStr := c.QueryParam("series"); seriesStr != "" { + if series, err := gstr.ParseBool(seriesStr); err != nil { + return nil, res.BadRequest(err.Error()) + } else { + filter.Series = null.Bool{Value: series, Set: true} + } + } + } + if filter.Type == model.SubjectTypeGame { + if platform := c.QueryParam("platform"); platform != "" { + // TODO: check if platform is valid + filter.Platform = null.String{Value: platform, Set: true} + } + } + if sort := c.QueryParam("sort"); sort != "" { + switch sort { + case "rank", "date": + filter.Sort = null.String{Value: sort, Set: true} + default: + return nil, res.BadRequest("unknown sort: " + sort) + } + } + if year, err := GetYearQuery(c); err != nil { + return nil, err + } else { + filter.Year = year + } + if month, err := GetMonthQuery(c); err != nil { + return nil, err + } else { + filter.Month = month + } + + return &filter, nil +} + +func GetYearQuery(c *echo.Context) (null.Int32, error) { + yearStr := c.QueryParam("year") + if yearStr == "" { + return null.Int32{}, nil + } + if year, err := gstr.ParseInt32(yearStr); err != nil { + return null.Int32{}, res.BadRequest(err.Error()) + } else { + if year < 1900 || year > 3000 { + return null.Int32{}, res.BadRequest("invalid year: " + yearStr) + } + return null.Int32{Value: year, Set: true}, nil + } +} + +func GetMonthQuery(c *echo.Context) (null.Int8, error) { + monthStr := c.QueryParam("month") + if monthStr == "" { + return null.Int8{}, nil + } + if month, err := gstr.ParseInt8(monthStr); err != nil { + return null.Int8{}, res.BadRequest(err.Error()) + } else { + if month < 1 || month > 12 { + return null.Int8{}, res.BadRequest("invalid month: " + monthStr) + } + return null.Int8{Value: month, Set: true}, nil + } +} diff --git a/web/handler/subject/get.go b/web/handler/subject/get.go index 5b8e3ca4e..6e6915f49 100644 --- a/web/handler/subject/get.go +++ b/web/handler/subject/get.go @@ -18,27 +18,23 @@ import ( "errors" "fmt" "net/http" + "time" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/trim21/errgo" - "go.uber.org/zap" "github.com/bangumi/server/domain/gerr" "github.com/bangumi/server/internal/episode" "github.com/bangumi/server/internal/model" - "github.com/bangumi/server/internal/pkg/compat" - "github.com/bangumi/server/internal/pkg/generic/slice" - "github.com/bangumi/server/internal/pkg/logger" "github.com/bangumi/server/internal/pkg/null" "github.com/bangumi/server/internal/subject" "github.com/bangumi/server/pkg/vars" - "github.com/bangumi/server/pkg/wiki" "github.com/bangumi/server/web/accessor" "github.com/bangumi/server/web/req" "github.com/bangumi/server/web/res" ) -func (h Subject) Get(c echo.Context) error { +func (h Subject) Get(c *echo.Context) error { u := accessor.GetFromCtx(c) id, err := req.ParseID(c.Param("id")) @@ -66,27 +62,19 @@ func (h Subject) Get(c echo.Context) error { return errgo.Wrap(err, "episode.Count") } - return c.JSON(http.StatusOK, convertModelSubject(s, totalEpisode)) -} - -func platformString(s model.Subject) *string { - platform, ok := vars.PlatformMap[s.TypeID][s.PlatformID] - if !ok && s.TypeID != 0 { - logger.Warn("unknown platform", - zap.Uint32("subject", s.ID), - zap.Uint8("type", s.TypeID), - zap.Uint16("platform", s.PlatformID), - ) - - return nil + metaTags, err := h.tag.Get(c.Request().Context(), s.ID, s.TypeID) + if err != nil { + return err } - v := platform.String() + if !s.NSFW { + res.SetCacheControl(c, res.CacheControlParams{Public: true, MaxAge: time.Hour}) + } - return &v + return c.JSON(http.StatusOK, res.ToSubjectV0(s, totalEpisode, metaTags)) } -func (h Subject) GetImage(c echo.Context) error { +func (h Subject) GetImage(c *echo.Context) error { u := accessor.GetFromCtx(c) id, err := req.ParseID(c.Param("id")) @@ -107,6 +95,8 @@ func (h Subject) GetImage(c echo.Context) error { return res.BadRequest("bad image type: " + c.QueryParam("type")) } + res.SetCacheControl(c, res.CacheControlParams{Public: true, MaxAge: time.Hour}) + if l == "" { return c.Redirect(http.StatusFound, res.DefaultImageURL) } @@ -114,61 +104,11 @@ func (h Subject) GetImage(c echo.Context) error { return c.Redirect(http.StatusFound, l) } -func convertModelSubject(s model.Subject, totalEpisode int64) res.SubjectV0 { - return res.SubjectV0{ - TotalEpisodes: totalEpisode, - ID: s.ID, - Image: res.SubjectImage(s.Image), - Summary: s.Summary, - Name: s.Name, - Platform: platformString(s), - NameCN: s.NameCN, - Date: null.NilString(s.Date), - Infobox: compat.V0Wiki(wiki.ParseOmitError(s.Infobox).NonZero()), - Volumes: s.Volumes, - Redirect: s.Redirect, - Eps: s.Eps, - Tags: slice.Map(s.Tags, func(tag model.Tag) res.SubjectTag { - return res.SubjectTag{ - Name: tag.Name, - Count: tag.Count, - } - }), - Collection: res.SubjectCollectionStat{ - OnHold: s.OnHold, - Wish: s.Wish, - Dropped: s.Dropped, - Collect: s.Collect, - Doing: s.Doing, - }, - TypeID: s.TypeID, - Locked: s.Locked(), - NSFW: s.NSFW, - Rating: res.Rating{ - Rank: s.Rating.Rank, - Total: s.Rating.Total, - Count: res.Count{ - Field1: s.Rating.Count.Field1, - Field2: s.Rating.Count.Field2, - Field3: s.Rating.Count.Field3, - Field4: s.Rating.Count.Field4, - Field5: s.Rating.Count.Field5, - Field6: s.Rating.Count.Field6, - Field7: s.Rating.Count.Field7, - Field8: s.Rating.Count.Field8, - Field9: s.Rating.Count.Field9, - Field10: s.Rating.Count.Field10, - }, - Score: s.Rating.Score, - }, - } -} - func readableRelation(destSubjectType model.SubjectType, relation uint16) string { var r, ok = vars.RelationMap[destSubjectType][relation] if !ok || relation == 1 { return model.SubjectTypeString(destSubjectType) } - return r.String() + return r.String(relation) } diff --git a/web/handler/subject/get_test.go b/web/handler/subject/get_test.go index 8f9b1fc91..1c0419e99 100644 --- a/web/handler/subject/get_test.go +++ b/web/handler/subject/get_test.go @@ -19,7 +19,7 @@ import ( "testing" "time" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/trim21/htest" @@ -31,6 +31,7 @@ import ( "github.com/bangumi/server/internal/pkg/null" "github.com/bangumi/server/internal/pkg/test" "github.com/bangumi/server/internal/subject" + "github.com/bangumi/server/internal/tag" "github.com/bangumi/server/web/accessor" subjectHandler "github.com/bangumi/server/web/handler/subject" "github.com/bangumi/server/web/internal/ctxkey" @@ -44,7 +45,7 @@ func TestSubject_Get(t *testing.T) { e := echo.New() g := e.Group("", func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { + return func(c *echo.Context) error { c.Set(ctxkey.User, &accessor.Accessor{Auth: auth.Auth{Login: true, RegTime: time.Time{}, ID: 1}, Login: true}) return next(c) } @@ -56,7 +57,10 @@ func TestSubject_Get(t *testing.T) { ep := mocks.NewEpisodeRepo(t) ep.EXPECT().Count(mock.Anything, subjectID, mock.Anything).Return(3, nil) - s, err := subjectHandler.New(nil, m, nil, nil, ep) + tagRepo := mocks.NewTagRepo(t) + tagRepo.EXPECT().Get(mock.Anything, mock.Anything, mock.Anything).Return([]tag.Tag{}, nil) + + s, err := subjectHandler.New(nil, m, nil, nil, ep, tagRepo) require.NoError(t, err) s.Routes(g) @@ -77,7 +81,7 @@ func TestSubject_Get_Redirect(t *testing.T) { app := test.GetWebApp(t, test.Mock{ - SubjectRepo: m, + SubjectCachedRepo: m, }, ) @@ -101,8 +105,8 @@ func TestSubject_Get_NSFW_200(t *testing.T) { app := test.GetWebApp(t, test.Mock{ - AuthRepo: mockAuth, - SubjectRepo: m, + AuthRepo: mockAuth, + SubjectCachedRepo: m, }, ) @@ -119,7 +123,7 @@ func TestSubject_Get_NSFW_404(t *testing.T) { Return(model.Subject{}, gerr.ErrSubjectNotFound) app := test.GetWebApp(t, - test.Mock{SubjectRepo: m}, + test.Mock{SubjectCachedRepo: m}, ) resp := htest.New(t, app).Get("/v0/subjects/7") @@ -134,7 +138,6 @@ func TestSubject_Get_bad_id(t *testing.T) { app := test.GetWebApp(t, test.Mock{SubjectRepo: m}) for _, path := range []string{"/v0/subjects/0", "/v0/subjects/-1", "/v0/subjects/a"} { - path := path t.Run(path, func(t *testing.T) { t.Parallel() @@ -151,10 +154,9 @@ func TestSubject_GetImage_302(t *testing.T) { m.EXPECT().Get(mock.Anything, model.SubjectID(1), mock.Anything).Return(model.Subject{ID: 1, Image: "temp"}, nil) m.EXPECT().Get(mock.Anything, model.SubjectID(3), mock.Anything).Return(model.Subject{ID: 1}, nil) - app := test.GetWebApp(t, test.Mock{SubjectRepo: m}) + app := test.GetWebApp(t, test.Mock{SubjectCachedRepo: m}) for _, imageType := range []string{"small", "grid", "large", "medium", "common"} { - imageType := imageType t.Run(imageType, func(t *testing.T) { t.Parallel() @@ -176,7 +178,7 @@ func TestSubject_GetImage_400(t *testing.T) { m := mocks.NewSubjectRepo(t) m.EXPECT().Get(mock.Anything, mock.Anything, mock.Anything).Return(model.Subject{Image: "temp"}, nil) - app := test.GetWebApp(t, test.Mock{SubjectRepo: m}) + app := test.GetWebApp(t, test.Mock{SubjectCachedRepo: m}) htest.New(t, app).Get("/v0/subjects/1/image").ExpectCode(http.StatusBadRequest) } diff --git a/web/handler/subject/related_characters.go b/web/handler/subject/related_characters.go index 23d41d6d3..7d3ac8f8b 100644 --- a/web/handler/subject/related_characters.go +++ b/web/handler/subject/related_characters.go @@ -18,8 +18,9 @@ import ( "context" "errors" "net/http" + "time" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/samber/lo" "github.com/trim21/errgo" @@ -35,7 +36,7 @@ import ( "github.com/bangumi/server/web/res" ) -func (h Subject) GetRelatedCharacters(c echo.Context) error { +func (h Subject) GetRelatedCharacters(c *echo.Context) error { u := accessor.GetFromCtx(c) subjectID, err := req.ParseID(c.Param("id")) if err != nil { @@ -65,12 +66,14 @@ func (h Subject) GetRelatedCharacters(c echo.Context) error { response[i] = res.SubjectRelatedCharacter{ Images: res.PersonImage(rel.Character.Image), Name: rel.Character.Name, - Relation: characterStaffString(rel.TypeID), + Summary: rel.Character.Summary, + Relation: res.CharacterStaffString(rel.TypeID), Actors: toActors(actors[rel.Character.ID]), Type: rel.Character.Type, ID: rel.Character.ID, } } + res.SetCacheControl(c, res.CacheControlParams{Public: true, MaxAge: time.Hour}) return c.JSON(http.StatusOK, response) } @@ -160,16 +163,3 @@ func toActors(persons []model.Person) []res.Actor { return actors } - -func characterStaffString(i uint8) string { - switch i { - case 1: - return "主角" - case 2: - return "配角" - case 3: - return "客串" - } - - return "" -} diff --git a/web/handler/subject/related_persons.go b/web/handler/subject/related_persons.go index af1d96196..5ed8212cd 100644 --- a/web/handler/subject/related_persons.go +++ b/web/handler/subject/related_persons.go @@ -17,8 +17,9 @@ package subject import ( "errors" "net/http" + "time" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/trim21/errgo" "github.com/bangumi/server/domain/gerr" @@ -30,7 +31,7 @@ import ( "github.com/bangumi/server/web/res" ) -func (h Subject) GetRelatedPersons(c echo.Context) error { +func (h Subject) GetRelatedPersons(c *echo.Context) error { u := accessor.GetFromCtx(c) id, err := req.ParseID(c.Param("id")) @@ -62,8 +63,10 @@ func (h Subject) GetRelatedPersons(c echo.Context) error { Career: rel.Person.Careers(), Type: rel.Person.Type, ID: rel.Person.ID, + Eps: rel.Eps, } } + res.SetCacheControl(c, res.CacheControlParams{Public: true, MaxAge: time.Hour}) return c.JSON(http.StatusOK, response) } diff --git a/web/handler/subject/related_subjects.go b/web/handler/subject/related_subjects.go index 1a74e4bd8..c7d8f462e 100644 --- a/web/handler/subject/related_subjects.go +++ b/web/handler/subject/related_subjects.go @@ -18,8 +18,9 @@ import ( "context" "errors" "net/http" + "time" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/trim21/errgo" "github.com/bangumi/server/domain" @@ -34,7 +35,7 @@ import ( "github.com/bangumi/server/web/res" ) -func (h Subject) GetRelatedSubjects(c echo.Context) error { +func (h Subject) GetRelatedSubjects(c *echo.Context) error { id, err := req.ParseID(c.Param("id")) if err != nil { return err @@ -63,6 +64,8 @@ func (h Subject) GetRelatedSubjects(c echo.Context) error { } } + res.SetCacheControl(c, res.CacheControlParams{Public: true, MaxAge: time.Hour}) + return c.JSON(http.StatusOK, response) } diff --git a/web/handler/subject/related_subjects_test.go b/web/handler/subject/related_subjects_test.go index 3d3a4eb12..97bc6a4d3 100644 --- a/web/handler/subject/related_subjects_test.go +++ b/web/handler/subject/related_subjects_test.go @@ -46,7 +46,7 @@ func TestSubject_GetRelatedSubjects(t *testing.T) { app := test.GetWebApp(t, test.Mock{ - SubjectRepo: m, + SubjectCachedRepo: m, }, ) diff --git a/web/handler/subject/subject.go b/web/handler/subject/subject.go index b1eec2e71..d3c38eece 100644 --- a/web/handler/subject/subject.go +++ b/web/handler/subject/subject.go @@ -15,28 +15,31 @@ package subject import ( - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/bangumi/server/internal/character" "github.com/bangumi/server/internal/episode" "github.com/bangumi/server/internal/person" "github.com/bangumi/server/internal/subject" + "github.com/bangumi/server/internal/tag" ) type Subject struct { person person.Service episode episode.Repo personRepo person.Repo - subject subject.Repo + subject subject.CachedRepo + tag tag.CachedRepo c character.Repo } func New( p person.Service, - subject subject.Repo, + subject subject.CachedRepo, personRepo person.Repo, c character.Repo, episode episode.Repo, + tag tag.CachedRepo, ) (Subject, error) { return Subject{ c: c, @@ -44,10 +47,12 @@ func New( personRepo: personRepo, subject: subject, person: p, + tag: tag, }, nil } func (h *Subject) Routes(g *echo.Group) { + g.GET("/subjects", h.Browse) g.GET("/subjects/:id", h.Get) g.GET("/subjects/:id/image", h.GetImage) g.GET("/subjects/:id/persons", h.GetRelatedPersons) diff --git a/web/handler/user/collection_test.go b/web/handler/user/collection_test.go index cc54dfeb1..e1a3b581a 100644 --- a/web/handler/user/collection_test.go +++ b/web/handler/user/collection_test.go @@ -20,7 +20,7 @@ import ( "net/http" "testing" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/trim21/htest" @@ -43,7 +43,7 @@ func TestUser_ListCollection(t *testing.T) { m := mocks.NewUserRepo(t) m.EXPECT().GetByName(mock.Anything, username).Return(user.User{ID: userID, UserName: username}, nil) - c := mocks.NewCollectionRepo(t) + c := mocks.NewCollectionsRepo(t) c.EXPECT().ListSubjectCollection(mock.Anything, userID, mock.Anything, mock.Anything, mock.Anything, 10, 0). Return([]collection.UserSubjectCollection{{SubjectID: subjectID, Type: 1}}, nil) c.EXPECT().CountSubjectCollections(mock.Anything, userID, mock.Anything, mock.Anything, mock.Anything). @@ -86,7 +86,7 @@ func TestUser_GetSubjectCollection(t *testing.T) { m := mocks.NewUserRepo(t) m.EXPECT().GetByName(mock.Anything, username).Return(user.User{ID: userID, UserName: username}, nil) - c := mocks.NewCollectionRepo(t) + c := mocks.NewCollectionsRepo(t) c.EXPECT().GetSubjectCollection(mock.Anything, userID, mock.Anything). Return(collection.UserSubjectCollection{SubjectID: subjectID, Type: 1}, nil) @@ -119,7 +119,7 @@ func TestUser_ListSubjectCollection_other_user(t *testing.T) { m := mocks.NewUserRepo(t) m.EXPECT().GetByName(mock.Anything, username).Return(user.User{ID: userID, UserName: username}, nil) - c := mocks.NewCollectionRepo(t) + c := mocks.NewCollectionsRepo(t) c.EXPECT().GetSubjectCollection(mock.Anything, userID, mock.Anything). Return(collection.UserSubjectCollection{SubjectID: subjectID, Private: true}, nil) @@ -130,3 +130,151 @@ func TestUser_ListSubjectCollection_other_user(t *testing.T) { Get(fmt.Sprintf("/v0/users/%s/collections/%d", username, subjectID)). ExpectCode(http.StatusNotFound) } + +func TestUser_GetPersonCollection(t *testing.T) { + t.Parallel() + const username = "ni" + const userID model.UserID = 7 + const personID model.PersonID = 9 + + m := mocks.NewUserRepo(t) + m.EXPECT().GetByName(mock.Anything, username).Return(user.User{ID: userID, UserName: username}, nil) + c := mocks.NewCollectionsRepo(t) + c.EXPECT().GetPersonCollection(mock.Anything, userID, mock.Anything, mock.Anything). + Return(collection.UserPersonCollection{UserID: userID, Category: "prsn", TargetID: personID}, nil) + + p := mocks.NewPersonRepo(t) + p.EXPECT().Get(mock.Anything, personID).Return(model.Person{ + ID: personID, + Name: "v", + }, nil) + + app := test.GetWebApp(t, test.Mock{UserRepo: m, CollectionRepo: c, PersonRepo: p}) + + var r res.PersonCollection + resp := htest.New(t, app). + Get(fmt.Sprintf("/v0/users/%s/collections/-/persons/%d", username, personID)). + JSON(&r). + ExpectCode(http.StatusOK) + + require.Equal(t, personID, r.ID, resp.BodyString()) + require.Equal(t, "v", r.Name, resp.BodyString()) +} + +func TestUser_ListPersonCollection(t *testing.T) { + t.Parallel() + const username = "ni" + const userID model.UserID = 7 + const personID model.PersonID = 9 + + m := mocks.NewUserRepo(t) + m.EXPECT().GetByName(mock.Anything, username).Return(user.User{ID: userID, UserName: username}, nil) + + c := mocks.NewCollectionsRepo(t) + c.EXPECT().ListPersonCollection(mock.Anything, userID, mock.Anything, 10, 0). + Return([]collection.UserPersonCollection{{UserID: userID, Category: "prsn", TargetID: personID}}, nil) + c.EXPECT().CountPersonCollections(mock.Anything, userID, mock.Anything). + Return(1, nil) + + p := mocks.NewPersonRepo(t) + p.EXPECT().GetByIDs(mock.Anything, mock.Anything).Return(map[model.PersonID]model.Person{ + personID: {ID: personID, Name: "v"}, + }, nil) + + app := test.GetWebApp(t, test.Mock{UserRepo: m, CollectionRepo: c, PersonRepo: p}) + + var r struct { + Data json.RawMessage `json:"data"` + Total int64 `json:"total"` + Limit int `json:"limit"` + Offset int `json:"offset"` + } + + resp := htest.New(t, app). + Query("limit", "10"). + Get(fmt.Sprintf("/v0/users/%s/collections/-/persons", username)). + JSON(&r). + ExpectCode(http.StatusOK) + + var data []res.PersonCollection + require.NoError(t, json.Unmarshal(r.Data, &data)) + + require.Len(t, data, 1) + + require.Equal(t, personID, data[0].ID, resp.BodyString()) + require.Equal(t, "v", data[0].Name, resp.BodyString()) +} + +func TestUser_GetCharacterCollection(t *testing.T) { + t.Parallel() + const username = "ni" + const userID model.UserID = 7 + const characterID model.CharacterID = 9 + + m := mocks.NewUserRepo(t) + m.EXPECT().GetByName(mock.Anything, username).Return(user.User{ID: userID, UserName: username}, nil) + c := mocks.NewCollectionsRepo(t) + c.EXPECT().GetPersonCollection(mock.Anything, userID, mock.Anything, mock.Anything). + Return(collection.UserPersonCollection{UserID: userID, Category: "crt", TargetID: characterID}, nil) + + p := mocks.NewCharacterRepo(t) + p.EXPECT().Get(mock.Anything, characterID).Return(model.Character{ + ID: characterID, + Name: "v", + }, nil) + + app := test.GetWebApp(t, test.Mock{UserRepo: m, CollectionRepo: c, CharacterRepo: p}) + + var r res.PersonCollection + resp := htest.New(t, app). + Get(fmt.Sprintf("/v0/users/%s/collections/-/characters/%d", username, characterID)). + JSON(&r). + ExpectCode(http.StatusOK) + + require.Equal(t, characterID, r.ID, resp.BodyString()) + require.Equal(t, "v", r.Name, resp.BodyString()) +} + +func TestUser_ListCharacterCollection(t *testing.T) { + t.Parallel() + const username = "ni" + const userID model.UserID = 7 + const characterID model.CharacterID = 9 + + m := mocks.NewUserRepo(t) + m.EXPECT().GetByName(mock.Anything, username).Return(user.User{ID: userID, UserName: username}, nil) + + c := mocks.NewCollectionsRepo(t) + c.EXPECT().ListPersonCollection(mock.Anything, userID, mock.Anything, 10, 0). + Return([]collection.UserPersonCollection{{UserID: userID, Category: "crt", TargetID: characterID}}, nil) + c.EXPECT().CountPersonCollections(mock.Anything, userID, mock.Anything). + Return(1, nil) + + p := mocks.NewCharacterRepo(t) + p.EXPECT().GetByIDs(mock.Anything, mock.Anything).Return(map[model.CharacterID]model.Character{ + characterID: {ID: characterID, Name: "v"}, + }, nil) + + app := test.GetWebApp(t, test.Mock{UserRepo: m, CollectionRepo: c, CharacterRepo: p}) + + var r struct { + Data json.RawMessage `json:"data"` + Total int64 `json:"total"` + Limit int `json:"limit"` + Offset int `json:"offset"` + } + + resp := htest.New(t, app). + Query("limit", "10"). + Get(fmt.Sprintf("/v0/users/%s/collections/-/characters", username)). + JSON(&r). + ExpectCode(http.StatusOK) + + var data []res.PersonCollection + require.NoError(t, json.Unmarshal(r.Data, &data)) + + require.Len(t, data, 1) + + require.Equal(t, characterID, data[0].ID, resp.BodyString()) + require.Equal(t, "v", data[0].Name, resp.BodyString()) +} diff --git a/web/handler/user/get.go b/web/handler/user/get.go index 5c32d9f5d..5f298b6ff 100644 --- a/web/handler/user/get.go +++ b/web/handler/user/get.go @@ -19,14 +19,14 @@ import ( "net/http" "strconv" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/trim21/errgo" "github.com/bangumi/server/domain/gerr" "github.com/bangumi/server/web/res" ) -func (h User) Get(c echo.Context) error { +func (h User) Get(c *echo.Context) error { username := c.Param("username") if username == "" { return res.BadRequest("missing require parameters `username`") @@ -49,7 +49,7 @@ func (h User) Get(c echo.Context) error { return c.JSON(http.StatusOK, r) } -func (h User) GetAvatar(c echo.Context) error { +func (h User) GetAvatar(c *echo.Context) error { username := c.Param("username") if username == "" { return res.BadRequest("missing require parameters `username`") diff --git a/web/handler/user/get_character_collection.go b/web/handler/user/get_character_collection.go new file mode 100644 index 000000000..c36b70b52 --- /dev/null +++ b/web/handler/user/get_character_collection.go @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +package user + +import ( + "errors" + "net/http" + + "github.com/labstack/echo/v5" + "github.com/trim21/errgo" + + "github.com/bangumi/server/domain/gerr" + "github.com/bangumi/server/internal/collections/domain/collection" + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/web/req" + "github.com/bangumi/server/web/res" +) + +func (h User) GetCharacterCollection(c *echo.Context) error { + username := c.Param("username") + if username == "" { + return res.BadRequest("missing require parameters `username`") + } + + characterID, err := req.ParseID(c.Param("character_id")) + if err != nil { + return err + } + + return h.getCharacterCollection(c, username, characterID) +} + +func (h User) getCharacterCollection(c *echo.Context, username string, characterID model.CharacterID) error { + const notFoundMessage = "character is not collected by user" + + character, err := h.character.Get(c.Request().Context(), characterID) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.ErrNotFound + } + + return errgo.Wrap(err, "failed to get character") + } + + u, err := h.user.GetByName(c.Request().Context(), username) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.NotFound("user doesn't exist or has been removed") + } + + return errgo.Wrap(err, "failed to get user by name") + } + + collect, err := h.collect.GetPersonCollection( + c.Request().Context(), + u.ID, + collection.PersonCollectCategoryCharacter, + characterID, + ) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.NotFound(notFoundMessage) + } + + return errgo.Wrap(err, "failed to get user's character collection") + } + + return c.JSON(http.StatusOK, res.ConvertModelCharacterCollection(collect, character)) +} diff --git a/web/handler/user/get_episode_collection.go b/web/handler/user/get_episode_collection.go index 2fc273b65..c2cc88e6f 100644 --- a/web/handler/user/get_episode_collection.go +++ b/web/handler/user/get_episode_collection.go @@ -18,7 +18,7 @@ import ( "errors" "net/http" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/trim21/errgo" "github.com/bangumi/server/domain/gerr" @@ -32,11 +32,12 @@ import ( ) type ResUserEpisodeCollection struct { - Episode res.Episode `json:"episode"` - Type collection.EpisodeCollection `json:"type"` + Episode res.Episode `json:"episode"` + Type collection.EpisodeCollection `json:"type"` + UpdatedAt int64 `json:"updated_at"` } -func (h User) GetEpisodeCollection(c echo.Context) error { +func (h User) GetEpisodeCollection(c *echo.Context) error { v := accessor.GetFromCtx(c) episodeID, err := req.ParseID(c.Param("episode_id")) if err != nil { @@ -56,14 +57,17 @@ func (h User) GetEpisodeCollection(c echo.Context) error { return errgo.Wrap(err, "collectionRepo.GetSubjectEpisodesCollection") } + ee := m[episodeID] + return c.JSON(http.StatusOK, ResUserEpisodeCollection{ - Episode: res.ConvertModelEpisode(e), - Type: m[episodeID].Type, + Episode: res.ConvertModelEpisode(e), + Type: ee.Type, + UpdatedAt: ee.UpdatedAt, }) } // GetSubjectEpisodeCollection return episodes with user's collection info. -func (h User) GetSubjectEpisodeCollection(c echo.Context) error { +func (h User) GetSubjectEpisodeCollection(c *echo.Context) error { v := accessor.GetFromCtx(c) subjectID, err := req.ParseID(c.Param("subject_id")) if err != nil { @@ -107,9 +111,11 @@ func (h User) GetSubjectEpisodeCollection(c echo.Context) error { var data []ResUserEpisodeCollection for _, episode := range episodes { + e := ec[episode.ID] data = append(data, ResUserEpisodeCollection{ - Episode: res.ConvertModelEpisode(episode), - Type: ec[episode.ID].Type, + Episode: res.ConvertModelEpisode(episode), + Type: e.Type, + UpdatedAt: e.UpdatedAt, }) } diff --git a/web/handler/user/get_episode_collection_test.go b/web/handler/user/get_episode_collection_test.go index 1d0f1205c..1dacb7683 100644 --- a/web/handler/user/get_episode_collection_test.go +++ b/web/handler/user/get_episode_collection_test.go @@ -18,7 +18,7 @@ import ( "net/http" "testing" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/trim21/htest" @@ -38,7 +38,7 @@ func TestUser_GetEpisodeCollection(t *testing.T) { mockAuth := mocks.NewAuthService(t) mockAuth.EXPECT().GetByToken(mock.Anything, mock.Anything).Return(auth.Auth{ID: 3}, nil) - c := mocks.NewCollectionRepo(t) + c := mocks.NewCollectionsRepo(t) c.EXPECT().GetSubjectEpisodesCollection(mock.Anything, mock.Anything, mock.Anything). Return(map[model.EpisodeID]collection.UserEpisodeCollection{}, nil) @@ -63,7 +63,7 @@ func TestUser_GetSubjectEpisodeCollection(t *testing.T) { mockAuth := mocks.NewAuthService(t) mockAuth.EXPECT().GetByToken(mock.Anything, mock.Anything).Return(auth.Auth{ID: 3}, nil) - c := mocks.NewCollectionRepo(t) + c := mocks.NewCollectionsRepo(t) c.EXPECT().GetSubjectEpisodesCollection(mock.Anything, mock.Anything, mock.Anything). Return(map[model.EpisodeID]collection.UserEpisodeCollection{}, nil) diff --git a/web/handler/user/get_person_collection.go b/web/handler/user/get_person_collection.go new file mode 100644 index 000000000..2842cfc09 --- /dev/null +++ b/web/handler/user/get_person_collection.go @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +package user + +import ( + "errors" + "net/http" + + "github.com/labstack/echo/v5" + "github.com/trim21/errgo" + + "github.com/bangumi/server/domain/gerr" + "github.com/bangumi/server/internal/collections/domain/collection" + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/web/req" + "github.com/bangumi/server/web/res" +) + +func (h User) GetPersonCollection(c *echo.Context) error { + username := c.Param("username") + if username == "" { + return res.BadRequest("missing require parameters `username`") + } + + personID, err := req.ParseID(c.Param("person_id")) + if err != nil { + return err + } + + return h.getPersonCollection(c, username, personID) +} + +func (h User) getPersonCollection(c *echo.Context, username string, personID model.PersonID) error { + const notFoundMessage = "person is not collected by user" + + person, err := h.person.Get(c.Request().Context(), personID) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.ErrNotFound + } + + return errgo.Wrap(err, "failed to get person") + } + + u, err := h.user.GetByName(c.Request().Context(), username) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.NotFound("user doesn't exist or has been removed") + } + + return errgo.Wrap(err, "failed to get user by name") + } + + collect, err := h.collect.GetPersonCollection( + c.Request().Context(), u.ID, collection.PersonCollectCategoryPerson, personID) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.NotFound(notFoundMessage) + } + + return errgo.Wrap(err, "failed to get person collect") + } + + return c.JSON(http.StatusOK, res.ConvertModelPersonCollection(collect, person)) +} diff --git a/web/handler/user/get_subject_collection.go b/web/handler/user/get_subject_collection.go index 9c104c078..9de611222 100644 --- a/web/handler/user/get_subject_collection.go +++ b/web/handler/user/get_subject_collection.go @@ -18,7 +18,7 @@ import ( "errors" "net/http" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/trim21/errgo" "github.com/bangumi/server/domain/gerr" @@ -30,7 +30,7 @@ import ( "github.com/bangumi/server/web/res" ) -func (h User) GetSubjectCollection(c echo.Context) error { +func (h User) GetSubjectCollection(c *echo.Context) error { username := c.Param("username") if username == "" { return res.BadRequest("missing require parameters `username`") @@ -44,7 +44,7 @@ func (h User) GetSubjectCollection(c echo.Context) error { return h.getSubjectCollection(c, username, subjectID) } -func (h User) getSubjectCollection(c echo.Context, username string, subjectID model.SubjectID) error { +func (h User) getSubjectCollection(c *echo.Context, username string, subjectID model.SubjectID) error { const notFoundMessage = "subject is not collected by user" v := accessor.GetFromCtx(c) diff --git a/web/handler/user/list_character_collections.go b/web/handler/user/list_character_collections.go new file mode 100644 index 000000000..83b5eab1d --- /dev/null +++ b/web/handler/user/list_character_collections.go @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +package user + +import ( + "errors" + "net/http" + + "github.com/labstack/echo/v5" + "github.com/trim21/errgo" + + "github.com/bangumi/server/domain/gerr" + "github.com/bangumi/server/internal/collections/domain/collection" + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/pkg/generic/slice" + "github.com/bangumi/server/internal/user" + "github.com/bangumi/server/web/req" + "github.com/bangumi/server/web/res" +) + +func (h User) ListCharacterCollection(c *echo.Context) error { + page, err := req.GetPageQuery(c, req.DefaultPageLimit, req.DefaultMaxPageLimit) + if err != nil { + return err + } + + username := c.Param("username") + if username == "" { + return res.BadRequest("missing require parameters `username`") + } + + u, err := h.user.GetByName(c.Request().Context(), username) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.NotFound("user doesn't exist or has been removed") + } + + return errgo.Wrap(err, "user.GetByName") + } + + return h.listCharacterCollection(c, u, page) +} + +func (h User) listCharacterCollection(c *echo.Context, u user.User, page req.PageQuery) error { + count, err := h.collect.CountPersonCollections(c.Request().Context(), u.ID, collection.PersonCollectCategoryCharacter) + if err != nil { + return errgo.Wrap(err, "failed to count user's character collections") + } + + if count == 0 { + return c.JSON(http.StatusOK, res.Paged{Data: []int{}, Total: count, Limit: page.Limit, Offset: page.Offset}) + } + + if err = page.Check(count); err != nil { + return err + } + + cols, err := h.collect.ListPersonCollection( + c.Request().Context(), + u.ID, collection.PersonCollectCategoryCharacter, + page.Limit, page.Offset) + if err != nil { + return errgo.Wrap(err, "failed to list user's person collections") + } + + characterIDs := slice.Map(cols, func(item collection.UserPersonCollection) model.PersonID { + return item.TargetID + }) + + characterMap, err := h.character.GetByIDs(c.Request().Context(), characterIDs) + if err != nil { + return errgo.Wrap(err, "failed to get persons") + } + + var data = make([]res.PersonCollection, 0, len(cols)) + + for _, col := range cols { + character, ok := characterMap[col.TargetID] + if !ok { + continue + } + data = append(data, res.ConvertModelCharacterCollection(col, character)) + } + + return c.JSON(http.StatusOK, res.Paged{ + Data: data, + Total: count, + Limit: page.Limit, + Offset: page.Offset, + }) +} diff --git a/web/handler/user/list_person_collections.go b/web/handler/user/list_person_collections.go new file mode 100644 index 000000000..9aeea15c8 --- /dev/null +++ b/web/handler/user/list_person_collections.go @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +package user + +import ( + "errors" + "net/http" + + "github.com/labstack/echo/v5" + "github.com/trim21/errgo" + + "github.com/bangumi/server/domain/gerr" + "github.com/bangumi/server/internal/collections/domain/collection" + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/pkg/generic/slice" + "github.com/bangumi/server/internal/user" + "github.com/bangumi/server/web/req" + "github.com/bangumi/server/web/res" +) + +func (h User) ListPersonCollection(c *echo.Context) error { + page, err := req.GetPageQuery(c, req.DefaultPageLimit, req.DefaultMaxPageLimit) + if err != nil { + return err + } + + username := c.Param("username") + if username == "" { + return res.BadRequest("missing require parameters `username`") + } + + u, err := h.user.GetByName(c.Request().Context(), username) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.NotFound("user doesn't exist or has been removed") + } + + return errgo.Wrap(err, "user.GetByName") + } + + return h.listPersonCollection(c, u, page) +} + +func (h User) listPersonCollection(c *echo.Context, u user.User, page req.PageQuery) error { + count, err := h.collect.CountPersonCollections(c.Request().Context(), u.ID, collection.PersonCollectCategoryPerson) + if err != nil { + return errgo.Wrap(err, "failed to count user's person collections") + } + + if count == 0 { + return c.JSON(http.StatusOK, res.Paged{Data: []int{}, Total: count, Limit: page.Limit, Offset: page.Offset}) + } + + if err = page.Check(count); err != nil { + return err + } + + cols, err := h.collect.ListPersonCollection( + c.Request().Context(), u.ID, collection.PersonCollectCategoryPerson, + page.Limit, page.Offset) + if err != nil { + return errgo.Wrap(err, "failed to list user's person collections") + } + + personIDs := slice.Map(cols, func(item collection.UserPersonCollection) model.PersonID { + return item.TargetID + }) + + personMap, err := h.person.GetByIDs(c.Request().Context(), personIDs) + if err != nil { + return errgo.Wrap(err, "failed to get persons") + } + + var data = make([]res.PersonCollection, 0, len(cols)) + + for _, col := range cols { + person, ok := personMap[col.TargetID] + if !ok { + continue + } + data = append(data, res.ConvertModelPersonCollection(col, person)) + } + + return c.JSON(http.StatusOK, res.Paged{ + Data: data, + Total: count, + Limit: page.Limit, + Offset: page.Offset, + }) +} diff --git a/web/handler/user/list_subject_collections.go b/web/handler/user/list_subject_collections.go index 7273e28a8..d2cc11f7d 100644 --- a/web/handler/user/list_subject_collections.go +++ b/web/handler/user/list_subject_collections.go @@ -18,7 +18,7 @@ import ( "errors" "net/http" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/trim21/errgo" "github.com/bangumi/server/domain/gerr" @@ -32,7 +32,7 @@ import ( "github.com/bangumi/server/web/res" ) -func (h User) ListSubjectCollection(c echo.Context) error { +func (h User) ListSubjectCollection(c *echo.Context) error { v := accessor.GetFromCtx(c) page, err := req.GetPageQuery(c, req.DefaultPageLimit, req.DefaultMaxPageLimit) if err != nil { @@ -69,7 +69,7 @@ func (h User) ListSubjectCollection(c echo.Context) error { } func (h User) listCollection( - c echo.Context, + c *echo.Context, u user.User, subjectType model.SubjectType, collectionType collection.SubjectCollection, diff --git a/web/handler/user/me.go b/web/handler/user/me.go index b0c48d45f..7883c442f 100644 --- a/web/handler/user/me.go +++ b/web/handler/user/me.go @@ -16,32 +16,50 @@ package user import ( "net/http" + "time" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/trim21/errgo" + "github.com/bangumi/server/internal/model" "github.com/bangumi/server/web/accessor" "github.com/bangumi/server/web/res" ) -func (h User) GetCurrent(c echo.Context) error { +type CurrentUser struct { + Avatar res.Avatar `json:"avatar"` + Sign string `json:"sign"` + URL string `json:"url"` + Username string `json:"username"` + Nickname string `json:"nickname"` + ID model.UserID `json:"id"` + UserGroup uint8 `json:"user_group"` + RegistrationTime time.Time `json:"reg_time"` + Email string `json:"email"` + TimeOffset int8 `json:"time_offset"` +} + +func (h User) GetCurrent(c *echo.Context) error { u := accessor.GetFromCtx(c) if !u.Login || u.ID == 0 { return res.Unauthorized("need Login") } - user, err := h.user.GetByID(c.Request().Context(), u.ID) + user, err := h.user.GetFullUser(c.Request().Context(), u.ID) if err != nil { return errgo.Wrap(err, "failed to get user") } - return c.JSON(http.StatusOK, res.User{ - ID: user.ID, - URL: "https://bgm.tv/user/" + user.UserName, - Username: user.UserName, - Nickname: user.NickName, - UserGroup: user.UserGroup, - Avatar: res.UserAvatar(user.Avatar), - Sign: user.Sign, + return c.JSON(http.StatusOK, CurrentUser{ + ID: user.ID, + URL: "https://bgm.tv/user/" + user.UserName, + Username: user.UserName, + Nickname: user.NickName, + UserGroup: user.UserGroup, + Avatar: res.UserAvatar(user.Avatar), + Sign: user.Sign, + RegistrationTime: user.RegistrationTime, + Email: user.Email, + TimeOffset: user.TimeOffset, }) } diff --git a/web/handler/user/patch_subject_collection.go b/web/handler/user/patch_subject_collection.go index b47e4bd59..67a4d8418 100644 --- a/web/handler/user/patch_subject_collection.go +++ b/web/handler/user/patch_subject_collection.go @@ -18,7 +18,7 @@ import ( "errors" "net/http" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/trim21/errgo" "github.com/bangumi/server/ctrl" @@ -31,7 +31,7 @@ import ( "github.com/bangumi/server/web/res" ) -func (h User) PatchSubjectCollection(c echo.Context) error { +func (h User) PatchSubjectCollection(c *echo.Context) error { subjectID, err := req.ParseID(c.Param("subject_id")) if err != nil { return err @@ -46,16 +46,42 @@ func (h User) PatchSubjectCollection(c echo.Context) error { return err } - return h.patchSubjectCollection(c, subjectID, r) + return h.patchSubjectCollection(c, subjectID, r, false) } func (h User) patchSubjectCollection( - c echo.Context, + c *echo.Context, subjectID model.SubjectID, r req.SubjectEpisodeCollectionPatch, + allowCreate bool, // 如果不存在 record 是否允许创建 +) error { + if err := h.updateOrCreateSubjectCollection(c, subjectID, r, allowCreate); err != nil { + switch { + case errors.Is(err, gerr.ErrSubjectNotCollected): + return res.NotFound("subject not collected") + case errors.Is(err, gerr.ErrSubjectNotFound): + return res.NotFound("subject not found") + case errors.Is(err, gerr.ErrBanned): + return res.Forbidden(err.Error()) + } + return errgo.Wrap(err, "ctrl.UpdateSubjectCollection") + } + + return c.NoContent(http.StatusNoContent) +} + +func (h User) updateOrCreateSubjectCollection( + c *echo.Context, + subjectID model.SubjectID, + r req.SubjectEpisodeCollectionPatch, + allowCreate bool, // 如果不存在 record 是否允许创建 ) error { u := accessor.GetFromCtx(c) + if u.Permission.UserBan { + return gerr.ErrBanned + } + s, err := h.subject.Get(c.Request().Context(), subjectID, subject.Filter{NSFW: null.Bool{Set: !u.AllowNSFW()}}) if err != nil { if errors.Is(err, gerr.ErrNotFound) { @@ -64,32 +90,27 @@ func (h User) patchSubjectCollection( return errgo.Wrap(err, "query.GetSubject") } + if s.Ban != 0 { + return res.NotFound("subject locked or merged") + } + if s.TypeID != model.SubjectTypeBook { if r.VolStatus.Set || r.EpStatus.Set { return res.BadRequest("can't set 'vol_status' or 'ep_status' on non-book subject") } } - err = h.ctrl.UpdateSubjectCollection(c.Request().Context(), u.Auth, subjectID, ctrl.UpdateCollectionRequest{ - IP: u.IP, - UID: u.ID, - VolStatus: r.VolStatus, - EpStatus: r.EpStatus, - Type: r.Type, - Tags: r.Tags, - Comment: r.Comment, - Rate: r.Rate, - Private: r.Private, - }) - if err != nil { - switch { - case errors.Is(err, gerr.ErrSubjectNotCollected): - return res.NotFound("subject not collected") - case errors.Is(err, gerr.ErrSubjectNotFound): - return res.NotFound("subject not found") - } - return errgo.Wrap(err, "ctrl.UpdateSubjectCollection") - } - - return c.NoContent(http.StatusNoContent) + return h.ctrl.UpdateSubjectCollection(c.Request().Context(), u.Auth, + model.Subject{ID: subjectID, TypeID: s.TypeID}, + ctrl.UpdateCollectionRequest{ + IP: u.IP, + UID: u.ID, + VolStatus: r.VolStatus, + EpStatus: r.EpStatus, + Type: r.Type, + Tags: r.Tags, + Comment: r.Comment, + Rate: r.Rate, + Private: r.Private, + }, allowCreate) } diff --git a/web/handler/user/patch_subject_collection_test.go b/web/handler/user/patch_subject_collection_test.go index 1a382266f..44cf7ecb1 100644 --- a/web/handler/user/patch_subject_collection_test.go +++ b/web/handler/user/patch_subject_collection_test.go @@ -22,13 +22,14 @@ import ( "testing" "time" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/samber/lo" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/trim21/htest" "github.com/bangumi/server/config" + "github.com/bangumi/server/domain/gerr" "github.com/bangumi/server/internal/auth" "github.com/bangumi/server/internal/collections/domain/collection" "github.com/bangumi/server/internal/mocks" @@ -41,27 +42,32 @@ func TestUser_PatchSubjectCollection(t *testing.T) { t.Parallel() const sid model.SubjectID = 8 const uid model.UserID = 1 + subject := model.Subject{ID: sid, TypeID: model.SubjectTypeAll} var s = &collection.Subject{} a := mocks.NewAuthService(t) a.EXPECT().GetByToken(mock.Anything, mock.Anything).Return(auth.Auth{ID: uid}, nil) - tl := mocks.NewTimeLineService(t) + tl := mocks.NewTimelineService(t) tl.EXPECT(). - ChangeSubjectCollection(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). + ChangeSubjectCollection(mock.Anything, + mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return(nil) - c := mocks.NewCollectionRepo(t) - c.EXPECT().UpdateSubjectCollection(mock.Anything, uid, sid, mock.Anything, mock.Anything, mock.Anything). + c := mocks.NewCollectionsRepo(t) + c.EXPECT().UpdateSubjectCollection(mock.Anything, uid, subject, mock.Anything, mock.Anything, mock.Anything). Run(func(ctx context.Context, userID uint32, - subjectID uint32, at time.Time, ip string, + subject model.Subject, at time.Time, ip string, update func(context.Context, *collection.Subject) (*collection.Subject, error)) { require.Equal(t, "0.0.0.0", ip) s = lo.Must(update(context.Background(), s)) }).Return(nil) + c.EXPECT().GetSubjectCollection(mock.Anything, mock.Anything, mock.Anything). + Return(collection.UserSubjectCollection{}, nil) + d, err := dam.New(config.AppConfig{NsfwWord: "", DisableWords: "test_content", BannedDomain: ""}) require.NoError(t, err) @@ -71,20 +77,87 @@ func TestUser_PatchSubjectCollection(t *testing.T) { Header(echo.HeaderAuthorization, "Bearer t"). BodyJSON(map[string]any{ "comment": "1 test_content 2", - "type": 1, + "type": 2, "private": true, "rate": 8, - "tags": []string{"q", "vv"}, + "tags": []string{"qq", "vv"}, }). Patch(fmt.Sprintf("/v0/users/-/collections/%d", sid)). ExpectCode(http.StatusNoContent) require.Equal(t, collection.CollectPrivacySelf, s.Privacy()) require.Equal(t, "1 test_content 2", s.Comment()) - require.EqualValues(t, []string{"q", "vv"}, s.Tags()) + require.EqualValues(t, []string{"qq", "vv"}, s.Tags()) require.EqualValues(t, 8, s.Rate()) } +func TestUser_PatchToNonExistsSubjectCollection(t *testing.T) { + t.Parallel() + const sid model.SubjectID = 8 + const uid model.UserID = 1 + subject := model.Subject{ID: sid, TypeID: model.SubjectTypeAll} + + a := mocks.NewAuthService(t) + a.EXPECT().GetByToken(mock.Anything, mock.Anything).Return(auth.Auth{ID: uid}, nil) + + tl := mocks.NewTimelineService(t) + + c := mocks.NewCollectionsRepo(t) + c.EXPECT().UpdateSubjectCollection(mock.Anything, uid, subject, mock.Anything, mock.Anything, mock.Anything). + Return(gerr.ErrSubjectNotCollected) + + d, err := dam.New(config.AppConfig{NsfwWord: "", DisableWords: "test_content", BannedDomain: ""}) + require.NoError(t, err) + + app := test.GetWebApp(t, test.Mock{CollectionRepo: c, AuthService: a, Dam: &d, TimeLineSrv: tl}) + + htest.New(t, app). + Header(echo.HeaderAuthorization, "Bearer t"). + BodyJSON(map[string]any{ + "comment": "1 test_content 2", + "type": 1, + "private": true, + "rate": 8, + "tags": []string{"qq", "vv"}, + }). + Patch(fmt.Sprintf("/v0/users/-/collections/%d", sid)). + ExpectCode(http.StatusNotFound) +} + +func TestUser_PatchSubjectCollection_badID(t *testing.T) { + t.Parallel() + + a := mocks.NewAuthService(t) + a.EXPECT().GetByToken(mock.Anything, mock.Anything).Return(auth.Auth{ID: 1}, nil) + + tl := mocks.NewTimelineService(t) + c := mocks.NewCollectionsRepo(t) + + d, err := dam.New(config.AppConfig{NsfwWord: "", DisableWords: "test_content", BannedDomain: ""}) + require.NoError(t, err) + + app := test.GetWebApp(t, test.Mock{CollectionRepo: c, AuthService: a, Dam: &d, TimeLineSrv: tl}) + + badURLs := []string{ + "/v0/users/-/collections/abc", + "/v0/users/-/collections/123_", + "/v0/users/-/collections/s123", + "/v0/users/-/collections/_123", + "/v0/users/-/collections/_abc", + "/v0/users/-/collections/_", + } + + for _, url := range badURLs { + htest.New(t, app). + Header(echo.HeaderAuthorization, "Bearer t"). + BodyJSON(map[string]any{ + "comment": "1 test_content 2", + }). + Patch(url). + ExpectCode(http.StatusBadRequest) + } +} + func TestUser_PatchSubjectCollection_bad(t *testing.T) { t.Parallel() const uid model.UserID = 1 @@ -100,7 +173,7 @@ func TestUser_PatchSubjectCollection_bad(t *testing.T) { htest.New(t, app). Header(echo.HeaderAuthorization, "Bearer t"). - BodyJSON(echo.Map{"rate": 11}). + BodyJSON(map[string]any{"rate": 11}). Patch(fmt.Sprintf("/v0/users/-/collections/%d", sid)). ExpectCode(http.StatusBadRequest) }) @@ -112,7 +185,7 @@ func TestUser_PatchSubjectCollection_bad(t *testing.T) { htest.New(t, app). Header(echo.HeaderAuthorization, "Bearer t"). - BodyJSON(echo.Map{"type": 0}). + BodyJSON(map[string]any{"type": 0}). Patch(fmt.Sprintf("/v0/users/-/collections/%d", sid)). ExpectCode(http.StatusBadRequest) }) @@ -124,7 +197,7 @@ func TestUser_PatchSubjectCollection_bad(t *testing.T) { htest.New(t, app). Header(echo.HeaderAuthorization, "Bearer t"). - BodyJSON(echo.Map{"tags": "vv qq"}). + BodyJSON(map[string]any{"tags": "vv qq"}). Patch(fmt.Sprintf("/v0/users/-/collections/%d", sid)). ExpectCode(http.StatusBadRequest) }) @@ -136,7 +209,7 @@ func TestUser_PatchSubjectCollection_bad(t *testing.T) { htest.New(t, app). Header(echo.HeaderAuthorization, "Bearer t"). - BodyJSON(echo.Map{"comment": strings.Repeat("vv qq", 200)}). + BodyJSON(map[string]any{"comment": strings.Repeat("vv qq", 200)}). Patch(fmt.Sprintf("/v0/users/-/collections/%d", sid)). ExpectCode(http.StatusBadRequest) }) diff --git a/web/handler/user/post_subject_collection.go b/web/handler/user/post_subject_collection.go new file mode 100644 index 000000000..652addd80 --- /dev/null +++ b/web/handler/user/post_subject_collection.go @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +package user + +import ( + "errors" + "net/http" + + "github.com/labstack/echo/v5" + "github.com/trim21/errgo" + + "github.com/bangumi/server/domain/gerr" + "github.com/bangumi/server/web/req" + "github.com/bangumi/server/web/res" +) + +func (h User) PostSubjectCollection(c *echo.Context) error { + subjectID, err := req.ParseID(c.Param("subject_id")) + if err != nil { + return err + } + + var r req.SubjectEpisodeCollectionPatch + if err = c.Echo().JSONSerializer.Deserialize(c, &r); err != nil { + return res.JSONError(c, err) + } + + if err = r.Validate(); err != nil { + return err + } + + // 与 PatchSubjectCollection 一致 + // 但允许创建,如果不存在 + if err := h.updateOrCreateSubjectCollection(c, subjectID, r, true); err != nil { + switch { + case errors.Is(err, gerr.ErrSubjectNotCollected): + return res.NotFound("subject not collected") + case errors.Is(err, gerr.ErrSubjectNotFound): + return res.NotFound("subject not found") + case errors.Is(err, gerr.ErrBanned): + return res.Forbidden(err.Error()) + } + return errgo.Wrap(err, "ctrl.UpdateSubjectCollection") + } + return c.NoContent(http.StatusAccepted) +} diff --git a/web/handler/user/post_subject_collection_test.go b/web/handler/user/post_subject_collection_test.go new file mode 100644 index 000000000..d36a5df65 --- /dev/null +++ b/web/handler/user/post_subject_collection_test.go @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +package user_test + +import ( + "context" + "fmt" + "net/http" + "strings" + "testing" + "time" + + "github.com/labstack/echo/v5" + "github.com/samber/lo" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/trim21/htest" + + "github.com/bangumi/server/config" + "github.com/bangumi/server/internal/auth" + "github.com/bangumi/server/internal/collections/domain/collection" + "github.com/bangumi/server/internal/mocks" + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/pkg/dam" + "github.com/bangumi/server/internal/pkg/test" +) + +func TestUser_PostSubjectCollection(t *testing.T) { + t.Parallel() + const sid model.SubjectID = 8 + const uid model.UserID = 1 + subject := model.Subject{ID: sid, TypeID: model.SubjectTypeAll} + + var s = &collection.Subject{} + + a := mocks.NewAuthService(t) + a.EXPECT().GetByToken(mock.Anything, mock.Anything).Return(auth.Auth{ID: uid}, nil) + + tl := mocks.NewTimelineService(t) + tl.EXPECT(). + ChangeSubjectCollection(mock.Anything, mock.Anything, mock.Anything, mock.Anything, + mock.Anything, mock.Anything, mock.Anything). + Return(nil) + + c := mocks.NewCollectionsRepo(t) + c.EXPECT().UpdateOrCreateSubjectCollection(mock.Anything, uid, subject, mock.Anything, mock.Anything, mock.Anything). + Run(func(ctx context.Context, userID uint32, + subject model.Subject, at time.Time, ip string, + update func(context.Context, *collection.Subject) (*collection.Subject, error)) { + require.Equal(t, "0.0.0.0", ip) + s = lo.Must(update(context.Background(), s)) + }). + Return(nil) + c.EXPECT().GetSubjectCollection(mock.Anything, mock.Anything, mock.Anything). + Return(collection.UserSubjectCollection{}, nil) + + d, err := dam.New(config.AppConfig{NsfwWord: "", DisableWords: "test_content", BannedDomain: ""}) + require.NoError(t, err) + + app := test.GetWebApp(t, test.Mock{CollectionRepo: c, AuthService: a, Dam: &d, TimeLineSrv: tl}) + + htest.New(t, app). + Header(echo.HeaderAuthorization, "Bearer t"). + BodyJSON(map[string]any{ + "comment": "1 test_content 2", + "type": 2, + "private": true, + "rate": 8, + "tags": []string{"qq", "vv"}, + }). + Post(fmt.Sprintf("/v0/users/-/collections/%d", sid)). + ExpectCode(http.StatusAccepted) + + require.Equal(t, collection.CollectPrivacySelf, s.Privacy()) + require.Equal(t, "1 test_content 2", s.Comment()) + require.EqualValues(t, []string{"qq", "vv"}, s.Tags()) + require.EqualValues(t, 8, s.Rate()) +} + +func TestUser_PostSubjectCollectionPartialData(t *testing.T) { + t.Parallel() + const sid model.SubjectID = 8 + const uid model.UserID = 1 + subject := model.Subject{ID: sid, TypeID: model.SubjectTypeAll} + + var s = &collection.Subject{} + + a := mocks.NewAuthService(t) + a.EXPECT().GetByToken(mock.Anything, mock.Anything).Return(auth.Auth{ID: uid}, nil) + + tl := mocks.NewTimelineService(t) + tl.EXPECT(). + ChangeSubjectCollection(mock.Anything, mock.Anything, mock.Anything, + mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(nil) + + c := mocks.NewCollectionsRepo(t) + c.EXPECT().UpdateOrCreateSubjectCollection(mock.Anything, uid, subject, mock.Anything, mock.Anything, mock.Anything). + Run(func(ctx context.Context, userID uint32, + subject model.Subject, at time.Time, ip string, + update func(context.Context, *collection.Subject) (*collection.Subject, error)) { + require.Equal(t, "0.0.0.0", ip) + s = lo.Must(update(context.Background(), s)) + }). + Return(nil) + c.EXPECT().GetSubjectCollection(mock.Anything, mock.Anything, mock.Anything). + Return(collection.UserSubjectCollection{}, nil) + + d, err := dam.New(config.AppConfig{NsfwWord: "", DisableWords: "test_content", BannedDomain: ""}) + require.NoError(t, err) + + app := test.GetWebApp(t, test.Mock{CollectionRepo: c, AuthService: a, Dam: &d, TimeLineSrv: tl}) + + htest.New(t, app). + Header(echo.HeaderAuthorization, "Bearer t"). + BodyJSON(map[string]any{ + "type": 3, + "comment": "1 test_content 2", + }). + Post(fmt.Sprintf("/v0/users/-/collections/%d", sid)). + ExpectCode(http.StatusAccepted) + + require.Equal(t, "1 test_content 2", s.Comment()) + require.EqualValues(t, []string(nil), s.Tags()) + require.Equal(t, collection.SubjectCollectionDoing, s.TypeID()) + + htest.New(t, app). + Header(echo.HeaderAuthorization, "Bearer t"). + BodyJSON(map[string]any{ + "type": 2, + }). + Post(fmt.Sprintf("/v0/users/-/collections/%d", sid)). + ExpectCode(http.StatusAccepted) + + require.Equal(t, collection.SubjectCollectionDone, s.TypeID()) + require.Equal(t, "1 test_content 2", s.Comment()) + require.EqualValues(t, []string(nil), s.Tags()) + + htest.New(t, app). + Header(echo.HeaderAuthorization, "Bearer t"). + BodyJSON(map[string]any{ + "tags": []string{"qq", "vv"}, + }). + Post(fmt.Sprintf("/v0/users/-/collections/%d", sid)). + ExpectCode(http.StatusAccepted) + + require.EqualValues(t, []string{"qq", "vv"}, s.Tags()) +} + +func TestUser_PostSubjectCollection_badID(t *testing.T) { + t.Parallel() + + a := mocks.NewAuthService(t) + a.EXPECT().GetByToken(mock.Anything, mock.Anything).Return(auth.Auth{ID: 1}, nil) + + tl := mocks.NewTimelineService(t) + c := mocks.NewCollectionsRepo(t) + + d, err := dam.New(config.AppConfig{NsfwWord: "", DisableWords: "test_content", BannedDomain: ""}) + require.NoError(t, err) + + app := test.GetWebApp(t, test.Mock{CollectionRepo: c, AuthService: a, Dam: &d, TimeLineSrv: tl}) + + badURLs := []string{ + "/v0/users/-/collections/abc", + "/v0/users/-/collections/123_", + "/v0/users/-/collections/s123", + "/v0/users/-/collections/_123", + "/v0/users/-/collections/_abc", + "/v0/users/-/collections/_", + } + + for _, url := range badURLs { + htest.New(t, app). + Header(echo.HeaderAuthorization, "Bearer t"). + BodyJSON(map[string]any{ + "comment": "1 test_content 2", + }). + Post(url). + ExpectCode(http.StatusBadRequest) + } +} + +func TestUser_PostSubjectCollection_bad(t *testing.T) { + t.Parallel() + const uid model.UserID = 1 + const sid model.SubjectID = 8 + + a := &mocks.AuthService{} + a.EXPECT().GetByToken(mock.Anything, mock.Anything).Return(auth.Auth{ID: uid}, nil) + + t.Run("bad rate", func(t *testing.T) { + t.Parallel() + + app := test.GetWebApp(t, test.Mock{AuthService: a}) + + htest.New(t, app). + Header(echo.HeaderAuthorization, "Bearer t"). + BodyJSON(map[string]any{"rate": 11}). + Post(fmt.Sprintf("/v0/users/-/collections/%d", sid)). + ExpectCode(http.StatusBadRequest) + }) + + t.Run("bad type", func(t *testing.T) { + t.Parallel() + + app := test.GetWebApp(t, test.Mock{AuthService: a}) + + htest.New(t, app). + Header(echo.HeaderAuthorization, "Bearer t"). + BodyJSON(map[string]any{"type": 0}). + Post(fmt.Sprintf("/v0/users/-/collections/%d", sid)). + ExpectCode(http.StatusBadRequest) + }) + + t.Run("bad type", func(t *testing.T) { + t.Parallel() + + app := test.GetWebApp(t, test.Mock{AuthService: a}) + + htest.New(t, app). + Header(echo.HeaderAuthorization, "Bearer t"). + BodyJSON(map[string]any{"tags": "vv qq"}). + Post(fmt.Sprintf("/v0/users/-/collections/%d", sid)). + ExpectCode(http.StatusBadRequest) + }) + + t.Run("too long comment", func(t *testing.T) { + t.Parallel() + + app := test.GetWebApp(t, test.Mock{AuthService: a}) + + htest.New(t, app). + Header(echo.HeaderAuthorization, "Bearer t"). + BodyJSON(map[string]any{"comment": strings.Repeat("vv qq", 200)}). + Post(fmt.Sprintf("/v0/users/-/collections/%d", sid)). + ExpectCode(http.StatusBadRequest) + }) +} diff --git a/web/handler/user/update_episode_collection.go b/web/handler/user/update_episode_collection.go index 9845d0444..047b795c2 100644 --- a/web/handler/user/update_episode_collection.go +++ b/web/handler/user/update_episode_collection.go @@ -19,7 +19,7 @@ import ( "fmt" "net/http" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/trim21/errgo" "github.com/bangumi/server/ctrl" @@ -56,7 +56,7 @@ func (r ReqEpisodeCollectionBatch) Validate() error { // PatchEpisodeCollectionBatch // // /v0/users/-/collections/:subject_id/episodes" -func (h User) PatchEpisodeCollectionBatch(c echo.Context) error { +func (h User) PatchEpisodeCollectionBatch(c *echo.Context) error { var r ReqEpisodeCollectionBatch if err := c.Echo().JSONSerializer.Deserialize(c, &r); err != nil { return res.JSONError(c, err) @@ -92,7 +92,7 @@ func (h User) PatchEpisodeCollectionBatch(c echo.Context) error { // PutEpisodeCollection // // /v0/users/-/collections/-/episodes/:episode_id -func (h User) PutEpisodeCollection(c echo.Context) error { +func (h User) PutEpisodeCollection(c *echo.Context) error { episodeID, err := req.ParseID(c.Param("episode_id")) if err != nil { return err diff --git a/web/handler/user/update_episode_collection_test.go b/web/handler/user/update_episode_collection_test.go index d142e06df..25b0e4038 100644 --- a/web/handler/user/update_episode_collection_test.go +++ b/web/handler/user/update_episode_collection_test.go @@ -21,7 +21,7 @@ import ( "testing" "time" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/trim21/htest" @@ -38,6 +38,7 @@ func TestUser_PatchEpisodeCollectionBatch(t *testing.T) { t.Parallel() const sid model.SubjectID = 8 const uid model.UserID = 1 + subject := model.Subject{ID: sid, TypeID: model.SubjectTypeAll} var eIDs []model.EpisodeID var eType collection.EpisodeCollection @@ -53,7 +54,7 @@ func TestUser_PatchEpisodeCollectionBatch(t *testing.T) { {ID: 4}, }, nil) - c := mocks.NewCollectionRepo(t) + c := mocks.NewCollectionsRepo(t) c.EXPECT().WithQuery(mock.Anything).Return(c) c.EXPECT().UpdateEpisodeCollection(mock.Anything, uid, sid, mock.Anything, mock.Anything, mock.Anything). Run(func(_ context.Context, _ model.UserID, _ model.SubjectID, @@ -61,7 +62,8 @@ func TestUser_PatchEpisodeCollectionBatch(t *testing.T) { eIDs = episodeIDs eType = collection }).Return(collection.UserSubjectEpisodesCollection{}, nil) - c.EXPECT().UpdateSubjectCollection(mock.Anything, uid, sid, mock.Anything, mock.Anything, mock.Anything).Return(nil) + c.EXPECT().UpdateSubjectCollection(mock.Anything, uid, subject, mock.Anything, mock.Anything, mock.Anything). + Return(nil) c.EXPECT().GetSubjectCollection(mock.Anything, uid, sid).Return(collection.UserSubjectCollection{SubjectID: sid}, nil) app := test.GetWebApp(t, test.Mock{EpisodeRepo: e, CollectionRepo: c, AuthService: a}) @@ -84,6 +86,7 @@ func TestUser_PutEpisodeCollection(t *testing.T) { const sid model.SubjectID = 8 const eid model.EpisodeID = 10 const uid model.UserID = 1 + subject := model.Subject{ID: sid, TypeID: model.SubjectTypeAll} var eIDs []model.EpisodeID var eType collection.EpisodeCollection @@ -94,7 +97,7 @@ func TestUser_PutEpisodeCollection(t *testing.T) { e := mocks.NewEpisodeRepo(t) e.EXPECT().Get(mock.Anything, eid).Return(episode.Episode{ID: eid, SubjectID: sid}, nil) - c := mocks.NewCollectionRepo(t) + c := mocks.NewCollectionsRepo(t) c.EXPECT().WithQuery(mock.Anything).Return(c) c.EXPECT().GetSubjectCollection(mock.Anything, uid, sid).Return(collection.UserSubjectCollection{SubjectID: sid}, nil) c.EXPECT().UpdateEpisodeCollection(mock.Anything, uid, sid, mock.Anything, mock.Anything, mock.Anything). @@ -103,7 +106,8 @@ func TestUser_PutEpisodeCollection(t *testing.T) { eIDs = episodeIDs eType = collection }).Return(collection.UserSubjectEpisodesCollection{}, nil) - c.EXPECT().UpdateSubjectCollection(mock.Anything, uid, sid, mock.Anything, mock.Anything, mock.Anything).Return(nil) + c.EXPECT().UpdateSubjectCollection(mock.Anything, uid, subject, mock.Anything, mock.Anything, mock.Anything). + Return(nil) app := test.GetWebApp(t, test.Mock{EpisodeRepo: e, CollectionRepo: c, AuthService: a}) diff --git a/web/handler/user/user.go b/web/handler/user/user.go index 59236045f..a85733224 100644 --- a/web/handler/user/user.go +++ b/web/handler/user/user.go @@ -19,6 +19,7 @@ import ( "github.com/bangumi/server/config" "github.com/bangumi/server/ctrl" + "github.com/bangumi/server/internal/character" "github.com/bangumi/server/internal/collections" "github.com/bangumi/server/internal/episode" "github.com/bangumi/server/internal/person" @@ -27,18 +28,20 @@ import ( ) type User struct { - ctrl ctrl.Ctrl - episode episode.Repo - person person.Service - collect collections.Repo - subject subject.CachedRepo - log *zap.Logger - user user.Repo - cfg config.AppConfig + ctrl ctrl.Ctrl + episode episode.Repo + character character.Repo + person person.Repo + collect collections.Repo + subject subject.CachedRepo + log *zap.Logger + user user.Repo + cfg config.AppConfig } func New( - p person.Service, + person person.Repo, + character character.Repo, user user.Repo, ctrl ctrl.Ctrl, subject subject.Repo, @@ -47,13 +50,14 @@ func New( log *zap.Logger, ) (User, error) { return User{ - ctrl: ctrl, - episode: episode, - collect: collect, - subject: subject, - user: user, - person: p, - log: log.Named("handler.User"), - cfg: config.AppConfig{}, + ctrl: ctrl, + episode: episode, + collect: collect, + subject: subject, + user: user, + person: person, + character: character, + log: log.Named("handler.User"), + cfg: config.AppConfig{}, }, nil } diff --git a/web/handler/user/user_test.go b/web/handler/user/user_test.go index 31a8d8a7f..9201023cf 100644 --- a/web/handler/user/user_test.go +++ b/web/handler/user/user_test.go @@ -36,7 +36,7 @@ func TestUser_Get(t *testing.T) { const uid model.UserID = 7 u := mocks.NewUserRepo(t) - u.EXPECT().GetByID(mock.Anything, uid).Return(user.User{ID: uid}, nil) + u.EXPECT().GetFullUser(mock.Anything, uid).Return(user.FullUser{ID: uid}, nil) a := mocks.NewAuthRepo(t) a.EXPECT().GetByToken(mock.Anything, "token").Return(auth.UserInfo{ID: uid}, nil) @@ -101,7 +101,6 @@ func TestUser_GetAvatar_302(t *testing.T) { app := test.GetWebApp(t, test.Mock{UserRepo: m}) for _, imageType := range []string{"large", "medium", "small"} { - imageType := imageType t.Run(imageType, func(t *testing.T) { t.Parallel() diff --git a/web/index.go b/web/index.go index 060330f16..99c1be5f8 100644 --- a/web/index.go +++ b/web/index.go @@ -18,7 +18,7 @@ import ( _ "embed" //nolint:revive "net/http" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/bangumi/server/config/env" ) @@ -28,12 +28,12 @@ var indexPageHTML string func indexPage() echo.HandlerFunc { if env.Production { - return func(c echo.Context) error { + return func(c *echo.Context) error { return c.Redirect(http.StatusFound, "https://github.com/bangumi/") } } - return func(c echo.Context) error { + return func(c *echo.Context) error { return c.HTML(http.StatusOK, indexPageHTML) } } diff --git a/web/handler/notification/count.go b/web/json.go similarity index 54% rename from web/handler/notification/count.go rename to web/json.go index a73408762..f6a3d1cd7 100644 --- a/web/handler/notification/count.go +++ b/web/json.go @@ -12,22 +12,29 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see -package notification +package web import ( - "net/http" + "github.com/bytedance/sonic/decoder" + "github.com/bytedance/sonic/encoder" + "github.com/labstack/echo/v5" +) - "github.com/labstack/echo/v4" - "github.com/trim21/errgo" +var _ echo.JSONSerializer = jsonSerializer{} - "github.com/bangumi/server/web/accessor" -) +type jsonSerializer struct { +} -func (h Notification) Count(c echo.Context) error { - auth := accessor.GetFromCtx(c) - count, err := h.notificationRepo.Count(c.Request().Context(), auth.ID) - if err != nil { - return errgo.Wrap(err, "failed to count notification") +func (j jsonSerializer) Serialize(c *echo.Context, i any, indent string) error { + enc := encoder.NewStreamEncoder(c.Response()) + if indent != "" { + enc.SetIndent("", indent) } - return c.JSON(http.StatusOK, count) + return enc.Encode(i) +} + +func (j jsonSerializer) Deserialize(c *echo.Context, i any) error { + dec := decoder.NewStreamDecoder(c.Request().Body) + dec.DisallowUnknownFields() + return dec.Decode(i) } diff --git a/web/mw/ban.lua b/web/mw/ban.lua new file mode 100644 index 000000000..6dcb544a4 --- /dev/null +++ b/web/mw/ban.lua @@ -0,0 +1,28 @@ +-- apply a rate limit by key +-- first check "long ban key" exists, when check request rate limit +-- if the request rate is too high, ban it with a long time. + +local LONG_BAN_KEY = KEYS[1]; +local RATE_KEY = KEYS[2]; + +local LONG_TIME = ARGV[1]; +local TIME_WINDOW = ARGV[2]; +local COUNT = ARGV[3]; + +local long_ban = redis.call('EXISTS', LONG_BAN_KEY) + +if long_ban == 1 then + return 1 +end + +local current = redis.call("incr", RATE_KEY) +if current == 1 then + redis.call("expire", RATE_KEY, tonumber(TIME_WINDOW)) +end + +if current <= tonumber(COUNT) then + return 0 +end + +redis.call("set", LONG_BAN_KEY, 1, 'ex', tonumber(LONG_TIME)) +return 1 diff --git a/web/mw/middleware.go b/web/mw/middleware.go index e29d9f746..3fcb94bba 100644 --- a/web/mw/middleware.go +++ b/web/mw/middleware.go @@ -15,7 +15,7 @@ package mw import ( - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/bangumi/server/web/accessor" "github.com/bangumi/server/web/res" @@ -24,7 +24,7 @@ import ( var errNeedLogin = res.Unauthorized("this API need authorization") func NeedLogin(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { + return func(c *echo.Context) error { if u := accessor.GetFromCtx(c); !u.Login { return errNeedLogin } diff --git a/web/mw/origin/new.go b/web/mw/origin/new.go index 2176c264f..9a07bc3f3 100644 --- a/web/mw/origin/new.go +++ b/web/mw/origin/new.go @@ -17,7 +17,7 @@ package origin import ( "net/http" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/bangumi/server/config/env" "github.com/bangumi/server/web/res" @@ -28,7 +28,7 @@ func New(allowed string) echo.MiddlewareFunc { return dev(allowed) } return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { + return func(c *echo.Context) error { if c.Request().Method == http.MethodGet { return next(c) } @@ -48,7 +48,7 @@ func New(allowed string) echo.MiddlewareFunc { func dev(_ string) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { + return func(c *echo.Context) error { return next(c) } } diff --git a/web/mw/rate-limit.go b/web/mw/rate-limit.go new file mode 100644 index 000000000..ab33e268c --- /dev/null +++ b/web/mw/rate-limit.go @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +package mw + +import ( + "context" + _ "embed" + "errors" + "fmt" + "net/http" + "strconv" + "time" + + "github.com/labstack/echo/v5" + "github.com/redis/rueidis" + "go.uber.org/zap" + + "github.com/bangumi/server/config" + "github.com/bangumi/server/internal/pkg/logger" + "github.com/bangumi/server/web/accessor" + "github.com/bangumi/server/web/res" +) + +//go:embed ban.lua +var rateLimitLua string + +func RateLimit(cfg config.AppConfig, r rueidis.Client) echo.MiddlewareFunc { + script := rueidis.NewLuaScript(rateLimitLua) + + args := []string{ + fmt.Sprintf("%d", cfg.RateLimit.LimitLongTime/time.Second), + fmt.Sprintf("%d", cfg.RateLimit.LimitWindow/time.Second), + fmt.Sprintf("%d", cfg.RateLimit.LimitCount), + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) error { + ip := c.RealIP() + u := accessor.GetFromCtx(c) + + var longBanKey string + var rateLimitKey string + + if u.Login { + longBanKey = "chii:rate-limit:long:3:u:" + strconv.FormatUint(uint64(u.ID), 10) + rateLimitKey = "chii:rate-limit:rate:3:u:" + strconv.FormatUint(uint64(u.ID), 10) + } else { + longBanKey = "chii:rate-limit:long:3:ip:" + ip + rateLimitKey = "chii:rate-limit:rate:3:ip:" + ip + } + + banned, err := script.Exec(c.Request().Context(), r, []string{longBanKey, rateLimitKey}, args).ToInt64() + if err != nil { + if !errors.Is(err, context.Canceled) { + logger.Error("failed to apply rate limit", zap.Error(err)) + } + return err + } + + if banned != 0 { + return c.JSON(http.StatusTooManyRequests, + res.Error{ + Title: "Too Many Request", + Description: `too many request, you have be rate limited`, + }, + ) + } + + return next(c) + } + } +} diff --git a/web/mw/recovery/debug.html b/web/mw/recovery/debug.html index 7eb204489..eff1f46a1 100644 --- a/web/mw/recovery/debug.html +++ b/web/mw/recovery/debug.html @@ -1,4 +1,4 @@ - + diff --git a/web/mw/recovery/dev.go b/web/mw/recovery/dev.go index c93928320..f708bb820 100644 --- a/web/mw/recovery/dev.go +++ b/web/mw/recovery/dev.go @@ -23,7 +23,7 @@ import ( "strconv" "strings" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "go.uber.org/zap" "github.com/bangumi/server/internal/pkg/logger" @@ -37,11 +37,11 @@ func dev() echo.MiddlewareFunc { log := logger.Copy().WithOptions(zap.AddCallerSkip(2)) // Return new handler return func(next echo.HandlerFunc) echo.HandlerFunc { //nolint:nonamedreturns - return func(c echo.Context) (err error) { + return func(c *echo.Context) (err error) { defer func() { if r := recover(); r != nil { c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8) - c.Response().Status = http.StatusInternalServerError + c.Response().WriteHeader(http.StatusInternalServerError) _, err = fmt.Fprintf(c.Response(), _debugHTML, r, takeStacktrace(2)) log.Error("panic: " + fmt.Sprintln(r)) diff --git a/web/mw/recovery/middle_test.go b/web/mw/recovery/middle_test.go index 78ed03f6e..ca6b9d91b 100644 --- a/web/mw/recovery/middle_test.go +++ b/web/mw/recovery/middle_test.go @@ -15,11 +15,12 @@ package recovery_test import ( + "context" "net/http" "net/http/httptest" "testing" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" "github.com/bangumi/server/web/mw/recovery" @@ -31,11 +32,11 @@ func TestPanicMiddleware(t *testing.T) { app.Use(recovery.New()) - app.GET("/", func(c echo.Context) error { + app.GET("/", func(c *echo.Context) error { panic("errInternal") }) - req := httptest.NewRequest(http.MethodGet, "/", nil) + req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/", nil) resp := httptest.NewRecorder() app.ServeHTTP(resp, req) diff --git a/web/mw/recovery/new.go b/web/mw/recovery/new.go index acb3f1202..1dde7a5a9 100644 --- a/web/mw/recovery/new.go +++ b/web/mw/recovery/new.go @@ -18,7 +18,7 @@ import ( "errors" "fmt" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "go.uber.org/zap" "github.com/bangumi/server/config/env" @@ -37,7 +37,7 @@ func New() echo.MiddlewareFunc { log := logger.Named("http.recovery") // Return new handler return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) (err error) { + return func(c *echo.Context) (err error) { defer func() { if r := recover(); r != nil { log.Error("recovery", zap.Any("recovery", r), logger.Ctx(c.Request().Context())) diff --git a/web/mw/referer/new.go b/web/mw/referer/new.go index 703f95c1b..cde70d032 100644 --- a/web/mw/referer/new.go +++ b/web/mw/referer/new.go @@ -17,7 +17,7 @@ package referer import ( "strings" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/bangumi/server/config/env" "github.com/bangumi/server/web/res" @@ -28,7 +28,7 @@ const HeaderReferer = "Referer" func New(referer string) echo.MiddlewareFunc { if env.Production { return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { + return func(c *echo.Context) error { ref := c.Request().Header.Get(HeaderReferer) if ref == "" || strings.HasPrefix(ref, referer) { return next(c) @@ -40,7 +40,7 @@ func New(referer string) echo.MiddlewareFunc { } return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { + return func(c *echo.Context) error { return next(c) } } diff --git a/web/mw/ua/new.go b/web/mw/ua/new.go index c7aec8a3d..948ee2c94 100644 --- a/web/mw/ua/new.go +++ b/web/mw/ua/new.go @@ -15,9 +15,11 @@ package ua import ( + "regexp" + "strconv" "strings" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/bangumi/server/web/res" ) @@ -29,7 +31,7 @@ const forbiddenMessage = "using HTTP request library's default User-Agent is for "https://github.com/bangumi/api/blob/master/docs-raw/user%20agent.md" func DisableDefaultHTTPLibrary(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { + return func(c *echo.Context) error { u := c.Request().UserAgent() if u == "" { return res.Forbidden("Please set a 'User-Agent'") @@ -43,6 +45,45 @@ func DisableDefaultHTTPLibrary(next echo.HandlerFunc) echo.HandlerFunc { } } +// DisableBrokenUA disallow known broken app send infinite requests. +func DisableBrokenUA(next echo.HandlerFunc) echo.HandlerFunc { + aniPattern := regexp.MustCompile(`^open-ani/ani/(\d+\.\d+\.\d+)\b`) + return func(c *echo.Context) error { + u := c.Request().UserAgent() + if u == "" { + return res.Forbidden("Please set a 'User-Agent'") + } + + if strings.HasPrefix(u, "open-ani/ani/") { + m := aniPattern.FindStringSubmatch(u) + + if len(m) < 2 { + return res.Forbidden(banAnimeko) + } + + version := m[1] + s := strings.Split(version, ".") + + if len(s) != 3 { + return res.Forbidden(banAnimeko) + } + + major, _ := strconv.Atoi(s[0]) + minor, _ := strconv.Atoi(s[1]) + patch, _ := strconv.Atoi(s[2]) + + if major < 4 || (major == 4 && minor < 8) || (major == 4 && minor == 8 && patch <= 1) { + return res.Forbidden(banAnimeko) + } + } + + return next(c) + } +} + +const banAnimeko = "Animeko version 4.8.1 and earlier contain a bug that causes continuous infinite requests." + + "Please update to version 4.8.2 or later to resolve this issue." + func isDefaultUA(u string) bool { for _, s := range disabledUA { if u == s { diff --git a/web/new.go b/web/new.go index e8fdaf10b..a02ed4091 100644 --- a/web/new.go +++ b/web/new.go @@ -19,12 +19,14 @@ import ( "fmt" "net" "net/http" + "net/http/pprof" "strconv" "strings" "time" - "github.com/labstack/echo/v4" - "github.com/labstack/echo/v4/middleware" + "github.com/google/uuid" + "github.com/labstack/echo/v5" + "github.com/labstack/echo/v5/middleware" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/trim21/errgo" @@ -45,8 +47,8 @@ const headerServerVersion = "x-server-version" func New() *echo.Echo { app := echo.New() app.HTTPErrorHandler = getDefaultErrorHandler() - app.HideBanner = true - app.HidePort = true + + app.JSONSerializer = jsonSerializer{} app.IPExtractor = func(request *http.Request) string { ip := request.Header.Get(cf.HeaderRequestIP) @@ -58,21 +60,43 @@ func New() *echo.Echo { return ip } + if env.Development { + // fasthttp bug, it uses an internal global variable and causing data race here + app.Static("/openapi/", "./openapi/") + app.GET("/debug", func(c *echo.Context) error { + return c.JSON(http.StatusOK, map[string]any{ + "ip": c.RealIP(), + }) + }) + } else { + app.StaticFS("/openapi/", openapi.Static) + } + + app.Use(recovery.New()) + app.Use(func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { + return func(c *echo.Context) error { metrics.RequestCount.Inc() start := time.Now() + c.Response().Header().Set(headerServerVersion, config.Version) err := next(c) sub := time.Since(start) metrics.RequestHistogram.Observe(sub.Seconds()) - c.Set(headerProcessTime, strconv.FormatInt(sub.Milliseconds(), 10)) - c.Set(headerServerVersion, config.Version) + c.Response().Header().Set(headerProcessTime, strconv.FormatInt(sub.Milliseconds(), 10)) + return err } }) + app.GET("/metrics", echo.WrapHandler(promhttp.Handler())) + app.GET("/debug/pprof/cmdline", echo.WrapHandler(http.HandlerFunc(pprof.Cmdline))) + app.GET("/debug/pprof/profile", echo.WrapHandler(http.HandlerFunc(pprof.Profile))) + app.GET("/debug/pprof/symbol", echo.WrapHandler(http.HandlerFunc(pprof.Symbol))) + app.GET("/debug/pprof/trace", echo.WrapHandler(http.HandlerFunc(pprof.Trace))) + app.GET("/debug/pprof/*", echo.WrapHandler(http.HandlerFunc(pprof.Index))) + if env.Development { app.Use(genFakeRequestID) } @@ -85,42 +109,30 @@ func New() *echo.Echo { })) app.Use(func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { + return func(c *echo.Context) error { + ctx, cancel := context.WithTimeout(context.WithoutCancel(c.Request().Context()), time.Minute) + defer cancel() + reqID := c.Request().Header.Get(cf.HeaderRequestID) - reqIP := c.RealIP() - c.SetRequest(c.Request(). - WithContext(context.WithValue(context.Background(), logger.RequestKey, &logger.RequestTrace{ - IP: reqIP, - ReqID: reqID, - }))) + if reqID == "" { + reqID = uuid.Must(uuid.NewV7()).String() + } + + c.SetRequest(c.Request().WithContext(context.WithValue(ctx, logger.RequestKey, &logger.RequestTrace{ + IP: c.RealIP(), + ReqID: reqID, + Path: c.Request().RequestURI, + }))) return next(c) } }) - app.Use(recovery.New()) - - app.GET("/metrics", echo.WrapHandler(promhttp.Handler())) - - addProfile(app) - - app.GET("/openapi", func(c echo.Context) error { + app.GET("/openapi", func(c *echo.Context) error { return c.Redirect(http.StatusFound, "/openapi/") }) - if env.Development { - // fasthttp bug, it uses an internal global variable and causing data race here - app.Static("/openapi/", "./openapi/") - app.GET("/debug", func(c echo.Context) error { - return c.JSON(http.StatusOK, echo.Map{ - "ip": c.RealIP(), - }) - }) - } else { - app.StaticFS("/openapi/", openapi.Static) - } - return app } @@ -129,8 +141,6 @@ func New() *echo.Echo { func NewTestingApp() *echo.Echo { app := echo.New() app.HTTPErrorHandler = getDefaultErrorHandler() - app.HideBanner = true - app.HidePort = true app.Use(genFakeRequestID) diff --git a/web/new_internal_test.go b/web/new_internal_test.go index 005b3aaf9..901966740 100644 --- a/web/new_internal_test.go +++ b/web/new_internal_test.go @@ -15,13 +15,14 @@ package web import ( + "context" "encoding/json" "errors" "net/http" "net/http/httptest" "testing" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" "github.com/bangumi/server/web/res" @@ -33,11 +34,11 @@ func TestDefaultErrorHandler_resError(t *testing.T) { app := echo.New() app.HTTPErrorHandler = getDefaultErrorHandler() - app.GET("/", func(c echo.Context) error { + app.GET("/", func(c *echo.Context) error { return res.BadRequest("mm") }) - req := httptest.NewRequest(http.MethodGet, "/", nil) + req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/", nil) resp := httptest.NewRecorder() app.ServeHTTP(resp, req) require.Equal(t, http.StatusBadRequest, resp.Code) @@ -58,11 +59,11 @@ func TestDefaultErrorHandler_internal(t *testing.T) { app.HTTPErrorHandler = getDefaultErrorHandler() - app.GET("/", func(c echo.Context) error { - return errors.New("mm") //nolint:goerr113 + app.GET("/", func(c *echo.Context) error { + return errors.New("mm") }) - req := httptest.NewRequest(http.MethodGet, "/", nil) + req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/", nil) resp := httptest.NewRecorder() app.ServeHTTP(resp, req) diff --git a/web/req/auth_test.go b/web/req/auth_test.go index 2cb9fedda..d519d114f 100644 --- a/web/req/auth_test.go +++ b/web/req/auth_test.go @@ -32,7 +32,6 @@ func TestLoginPass(t *testing.T) { } validate := validator.New() for i, login := range testCase { - login := login t.Run(fmt.Sprintf("success %d", i), func(t *testing.T) { t.Parallel() require.NoError(t, validate.Struct(login)) @@ -50,7 +49,6 @@ func TestLoginErr(t *testing.T) { } validate := validator.New() for i, login := range testCase { - login := login t.Run(fmt.Sprintf("fail %d", i), func(t *testing.T) { t.Parallel() require.Error(t, validate.Struct(login)) diff --git a/web/req/collection.go b/web/req/collection.go index 461a6b312..b8ade56f4 100644 --- a/web/req/collection.go +++ b/web/req/collection.go @@ -15,10 +15,12 @@ package req import ( + "fmt" "strings" "unicode/utf8" "github.com/samber/lo" + "golang.org/x/text/unicode/norm" "github.com/bangumi/server/internal/collections/domain/collection" "github.com/bangumi/server/internal/pkg/dam" @@ -41,26 +43,47 @@ type SubjectEpisodeCollectionPatch struct { } func (v *SubjectEpisodeCollectionPatch) Validate() error { + if v.Type.Set { + if !v.Type.Value.IsValid() { + return res.BadRequest("invalid type") + } + } + if v.Rate.Set { if v.Rate.Value > 10 { return res.BadRequest("rate overflow") } } - if len(v.Tags) > 0 { - if !lo.EveryBy(v.Tags, dam.AllPrintableChar) { - return res.BadRequest("invisible character are included in tags") + if v.Tags != nil { + if len(v.Tags) > 10 { + return res.BadRequest("最多允许 10 个标签") + } + + v.Tags = lo.Map(v.Tags, func(item string, index int) string { + return norm.NFKC.String(item) + }) + } + + for _, tag := range v.Tags { + if utf8.RuneCountInString(tag) < 2 { + return res.BadRequest("tag 最短为两个字") + } + + if !dam.ValidateTag(tag) { + return res.BadRequest(fmt.Sprintf("invalid tag: %q", tag)) } } if v.Comment.Set { + v.Comment.Value = norm.NFC.String(v.Comment.Value) + v.Comment.Value = strings.TrimSpace(v.Comment.Value) if !dam.AllPrintableChar(v.Comment.Value) { return res.BadRequest("invisible character are included in comment") } - v.Comment.Value = strings.TrimSpace(v.Comment.Value) - if utf8.RuneCountInString(v.Comment.Value) > 200 { - return res.BadRequest("comment too lang, only allow less equal than 200 characters") + if utf8.RuneCountInString(v.Comment.Value) > 380 { + return res.BadRequest("comment too long, only allow less equal than 380 characters") } } diff --git a/web/req/content_type.go b/web/req/content_type.go index 64143e9e5..284d14b78 100644 --- a/web/req/content_type.go +++ b/web/req/content_type.go @@ -17,14 +17,15 @@ package req import ( "net/http" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/bangumi/server/web/res" ) func JSON(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { + return func(c *echo.Context) error { contentType := c.Request().Header.Get(echo.HeaderContentType) + //nolint:staticcheck if contentType == echo.MIMEApplicationJSON || contentType == echo.MIMEApplicationJSONCharsetUTF8 { return next(c) } diff --git a/web/req/index.go b/web/req/index.go index af50b9c89..b7b2887de 100644 --- a/web/req/index.go +++ b/web/req/index.go @@ -23,7 +23,7 @@ type IndexBasicInfo struct { type IndexAddSubject struct { SubjectID model.SubjectID `json:"subject_id"` - *IndexSubjectInfo + IndexSubjectInfo } type IndexSubjectInfo struct { diff --git a/web/req/page.go b/web/req/page.go index c0e1de94b..d74c53986 100644 --- a/web/req/page.go +++ b/web/req/page.go @@ -17,7 +17,7 @@ package req import ( "strconv" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/bangumi/server/web/res" ) @@ -35,7 +35,43 @@ func (q PageQuery) Check(count int64) error { return nil } -func GetPageQuery(c echo.Context, defaultLimit int, maxLimit int) (PageQuery, error) { +// GetPageQuerySoftLimit apply soft limit on query without error. +func GetPageQuerySoftLimit(c *echo.Context, defaultLimit int, maxLimit int) (PageQuery, error) { + q := PageQuery{Limit: defaultLimit} + var err error + + raw := c.QueryParam("limit") + if raw != "" { + q.Limit, err = strconv.Atoi(raw) + if err != nil { + return q, res.BadRequest("can't parse query args limit as int: " + strconv.Quote(raw)) + } + + if q.Limit <= 0 { + return q, res.BadRequest("limit should be greater than zero") + } + + if q.Limit > maxLimit { + q.Limit = maxLimit + } + } + + raw = c.QueryParam("offset") + if raw != "" { + q.Offset, err = strconv.Atoi(raw) + if err != nil { + return q, res.BadRequest("can't parse query args offset as int: " + strconv.Quote(raw)) + } + + if q.Offset < 0 { + return q, res.BadRequest("offset should be greater than or equal to 0") + } + } + + return q, nil +} + +func GetPageQuery(c *echo.Context, defaultLimit int, maxLimit int) (PageQuery, error) { q := PageQuery{Limit: defaultLimit} var err error diff --git a/web/req/query_parse.go b/web/req/query_parse.go index e9a71c456..7afac3334 100644 --- a/web/req/query_parse.go +++ b/web/req/query_parse.go @@ -21,7 +21,7 @@ import ( "github.com/bangumi/server/internal/episode" "github.com/bangumi/server/internal/model" "github.com/bangumi/server/internal/pkg/gstr" - "github.com/bangumi/server/internal/pm" + "github.com/bangumi/server/pkg/vars" "github.com/bangumi/server/web/res" ) @@ -47,6 +47,24 @@ func ParseSubjectType(s string) (model.SubjectType, error) { return 0, res.BadRequest(strconv.Quote(s) + " is not a valid subject type") } +func ParseSubjectCategory(stype model.SubjectType, s string) (uint16, error) { + if s == "" { + return 0, res.BadRequest("subject category is empty") + } + platforms, ok := vars.PlatformMap[stype] + if !ok { + return 0, res.BadRequest("bad subject type: " + strconv.Quote(s)) + } + v, err := gstr.ParseUint16(s) + if err != nil { + return 0, res.BadRequest("bad subject category: " + strconv.Quote(s)) + } + if _, ok := platforms[v]; !ok { + return 0, res.BadRequest("bad subject category: " + strconv.Quote(s)) + } + return v, nil +} + func ParseID(s string) (model.CharacterID, error) { if s == "" { return 0, errMissingID @@ -104,17 +122,3 @@ func ParseEpTypeOptional(s string) (*episode.Type, error) { return nil, res.BadRequest(strconv.Quote(s) + " is not valid episode type") } - -func ParsePrivateMessageFolder(s string) (pm.FolderType, error) { - v := pm.FolderType(s) - switch v { - case pm.FolderTypeInbox, - pm.FolderTypeOutbox: - return v, nil - } - return v, res.BadRequest( - "folder must be " + - string(pm.FolderTypeInbox) + - " or " + - string(pm.FolderTypeOutbox)) -} diff --git a/internal/pkg/random/string_internal_test.go b/web/res/cache-control.go similarity index 57% rename from internal/pkg/random/string_internal_test.go rename to web/res/cache-control.go index 166cfb5ee..a5231ceb9 100644 --- a/internal/pkg/random/string_internal_test.go +++ b/web/res/cache-control.go @@ -12,17 +12,31 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see -package random +package res import ( - "testing" + "bytes" + "fmt" + "time" - "github.com/stretchr/testify/require" + "github.com/labstack/echo/v5" ) -func TestConst(t *testing.T) { - t.Parallel() +type CacheControlParams struct { + Public bool + MaxAge time.Duration +} + +func (c CacheControlParams) String() string { + buf := bytes.NewBuffer(make([]byte, 0, 20)) + + _, _ = buf.WriteString("public, ") + + _, _ = fmt.Fprintf(buf, "max-age=%d", c.MaxAge/time.Second) + + return buf.String() +} - require.EqualValues(t, len(base62Chars), base62CharsLength) - require.EqualValues(t, 255-(256%len(base62Chars)), base62MaxByte) +func SetCacheControl(c *echo.Context, val CacheControlParams) { + c.Response().Header().Set(echo.HeaderCacheControl, val.String()) } diff --git a/web/res/character.go b/web/res/character.go index 703b55d37..51e99e85f 100644 --- a/web/res/character.go +++ b/web/res/character.go @@ -14,7 +14,13 @@ package res -import "github.com/bangumi/server/internal/model" +import ( + wiki "github.com/bangumi/wiki-parser-go" + + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/pkg/compat" + "github.com/bangumi/server/internal/pkg/null" +) type CharacterV0 struct { BirthMon *uint8 `json:"birth_mon"` @@ -25,7 +31,7 @@ type CharacterV0 struct { Images PersonImages `json:"images"` Summary string `json:"summary"` Name string `json:"name"` - Infobox v0wiki `json:"infobox"` + Infobox V0wiki `json:"infobox"` Stat Stat `json:"stat"` ID model.CharacterID `json:"id"` Redirect model.CharacterID `json:"-"` @@ -40,15 +46,41 @@ var GenderMap = map[uint8]string{ 2: "female", } +//nolint:gochecknoglobals +var characterStaffMap = map[uint8]string{ + 1: "主角", + 2: "配角", + 3: "客串", + 4: "闲角", + 5: "旁白", + 6: "声库", +} + func CharacterStaffString(i uint8) string { - switch i { - case 1: - return "主角" - case 2: - return "配角" - case 3: - return "客串" - } + return characterStaffMap[i] +} - return "" +func ConvertModelCharacter(s model.Character) CharacterV0 { + img := PersonImage(s.Image) + + return CharacterV0{ + ID: s.ID, + Type: s.Type, + Name: s.Name, + NSFW: s.NSFW, + Images: img, + Summary: s.Summary, + Infobox: compat.V0Wiki(wiki.ParseOmitError(s.Infobox).NonZero()), + Gender: null.NilString(GenderMap[s.FieldGender]), + BloodType: null.NilUint8(s.FieldBloodType), + BirthYear: null.NilUint16(s.FieldBirthYear), + BirthMon: null.NilUint8(s.FieldBirthMon), + BirthDay: null.NilUint8(s.FieldBirthDay), + Stat: Stat{ + Comments: s.CommentCount, + Collects: s.CollectCount, + }, + Redirect: s.Redirect, + Locked: s.Locked, + } } diff --git a/web/res/collection.go b/web/res/collection.go index 7cb639ec9..84534d55b 100644 --- a/web/res/collection.go +++ b/web/res/collection.go @@ -51,3 +51,33 @@ func ConvertModelSubjectCollection(c collection.UserSubjectCollection, subject S Subject: subject, } } + +type PersonCollection struct { + ID uint32 `json:"id"` + Type uint8 `json:"type"` + Name string `json:"name"` + Images PersonImages `json:"images"` + CreatedAt time.Time `json:"created_at"` +} + +func ConvertModelPersonCollection(c collection.UserPersonCollection, person model.Person) PersonCollection { + img := PersonImage(person.Image) + return PersonCollection{ + ID: person.ID, + Type: person.Type, + Name: person.Name, + Images: img, + CreatedAt: c.CreatedAt, + } +} + +func ConvertModelCharacterCollection(c collection.UserPersonCollection, character model.Character) PersonCollection { + img := PersonImage(character.Image) + return PersonCollection{ + ID: character.ID, + Type: character.Type, + Name: character.Name, + Images: img, + CreatedAt: c.CreatedAt, + } +} diff --git a/web/res/error.go b/web/res/error.go index 3aa2ad039..fa9c04062 100644 --- a/web/res/error.go +++ b/web/res/error.go @@ -20,8 +20,9 @@ import ( "net/http" "strconv" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" + "github.com/bangumi/server/web/req/cf" "github.com/bangumi/server/web/util" ) @@ -31,6 +32,7 @@ var ErrNotFound = NewError(http.StatusNotFound, "resource can't be found in the type Error struct { Title string `json:"title"` Details any `json:"details,omitempty"` + RequestID string `json:"request_id,omitempty"` Description string `json:"description"` } @@ -50,33 +52,34 @@ func (e HTTPError) Error() string { } //nolint:errorlint -func JSONError(c echo.Context, err error) error { +func JSONError(c *echo.Context, err error) error { if ute, ok := err.(*json.UnmarshalTypeError); ok { - return c.JSON(http.StatusInternalServerError, Error{ - Title: "Internal Server Error", + return c.JSON(http.StatusBadRequest, Error{ + Title: "JSON Error", Description: fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset), }) } if se, ok := err.(*json.SyntaxError); ok { - return c.JSON(http.StatusInternalServerError, Error{ - Title: "Internal Server Error", + return c.JSON(http.StatusBadRequest, Error{ + Title: "JSON Error", Description: fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error()), }) } return c.JSON(http.StatusBadRequest, Error{ - Title: "BodyJSON Error", + Title: "JSON Error", Description: "can't decode request body as json or value doesn't match expected type", Details: util.DetailWithErr(c, err), }) } -func InternalError(c echo.Context, err error, message string) error { +func InternalError(c *echo.Context, err error, message string) error { return c.JSON(http.StatusInternalServerError, Error{ Title: "Internal Server Error", Description: message, + RequestID: c.Request().Header.Get(cf.HeaderRequestID), Details: util.DetailWithErr(c, err), }) } diff --git a/web/res/image.go b/web/res/image.go index 3b5fc5c0d..7f870bec2 100644 --- a/web/res/image.go +++ b/web/res/image.go @@ -93,7 +93,7 @@ func PersonImage(s string) PersonImages { Large: "https://lain.bgm.tv/pic/crt/l/" + s, Small: "https://lain.bgm.tv/r/100/pic/crt/l/" + s, - Grid: "https://lain.bgm.tv/r/200/pic/crt/l/" + s, + Grid: "https://lain.bgm.tv/pic/crt/g/" + s, Medium: "https://lain.bgm.tv/r/400/pic/crt/l/" + s, } } diff --git a/web/res/index.go b/web/res/index.go index 850741ce2..395d0f51a 100644 --- a/web/res/index.go +++ b/web/res/index.go @@ -30,8 +30,10 @@ type Index struct { Total uint32 `json:"total"` ID model.IndexID `json:"id"` Stat Stat `json:"stat"` - Ban bool `json:"ban"` NSFW bool `json:"nsfw" doc:"if index contains any nsfw subjects"` + + // Deprecated: this will always be false. + Ban bool `json:"ban"` } func IndexModelToResponse(i *model.Index, u user.User) Index { @@ -50,7 +52,6 @@ func IndexModelToResponse(i *model.Index, u user.User) Index { Comments: i.Comments, Collects: i.Collects, }, - Ban: i.Ban, NSFW: i.NSFW, } } diff --git a/web/res/person.go b/web/res/person.go index f08c8f101..e6e449bab 100644 --- a/web/res/person.go +++ b/web/res/person.go @@ -17,10 +17,11 @@ package res import ( "time" + wiki "github.com/bangumi/wiki-parser-go" + "github.com/bangumi/server/internal/model" "github.com/bangumi/server/internal/pkg/compat" "github.com/bangumi/server/internal/pkg/null" - "github.com/bangumi/server/pkg/wiki" ) type PersonV0 struct { @@ -34,7 +35,7 @@ type PersonV0 struct { Summary string `json:"summary"` Name string `json:"name"` Img string `json:"img"` - Infobox v0wiki `json:"infobox"` + Infobox V0wiki `json:"infobox"` Career []string `json:"career"` Stat Stat `json:"stat"` Redirect model.PersonID `json:"-"` diff --git a/web/res/pm.go b/web/res/pm.go deleted file mode 100644 index 9fe196376..000000000 --- a/web/res/pm.go +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see - -package res - -import ( - "time" - - "github.com/samber/lo" - - "github.com/bangumi/server/internal/model" - "github.com/bangumi/server/internal/pm" - "github.com/bangumi/server/internal/user" -) - -type PrivateMessage struct { - CreatedAt time.Time `json:"created_at"` - RelatedMessage *PrivateMessage `json:"related_message,omitempty"` - Sender *User `json:"sender,omitempty"` - Receiver *User `json:"receiver,omitempty"` - Title string `json:"title"` - Content string `json:"content"` - ID uint32 `json:"id"` - New bool `json:"new"` -} - -type PrivateMessageTypeCounts struct { - Unread int64 `json:"unread"` - Inbox int64 `json:"inbox"` - Outbox int64 `json:"outbox"` -} - -func ConvertModelPrivateMessage(item pm.PrivateMessage, users map[model.UserID]user.User) PrivateMessage { - msg := PrivateMessage{ - CreatedAt: item.CreatedTime, - Title: item.Title, - Content: item.Content, - ID: item.ID, - New: item.New, - } - if users != nil { - if u, ok := users[item.SenderID]; ok { - msg.Sender = lo.ToPtr(ConvertModelUser(u)) - } - if u, ok := users[item.ReceiverID]; ok { - msg.Receiver = lo.ToPtr(ConvertModelUser(u)) - } - } - return msg -} - -func ConvertModelPrivateMessageListItem( - item pm.PrivateMessageListItem, - users map[model.UserID]user.User, -) PrivateMessage { - relatedMsg := ConvertModelPrivateMessage(item.Main, nil) - msg := ConvertModelPrivateMessage(item.Self, users) - msg.RelatedMessage = &relatedMsg - return msg -} diff --git a/web/res/subject.go b/web/res/subject.go index b9a26aae5..0af10cb1f 100644 --- a/web/res/subject.go +++ b/web/res/subject.go @@ -17,41 +17,51 @@ package res import ( "time" + wiki "github.com/bangumi/wiki-parser-go" "github.com/samber/lo" + "go.uber.org/zap" "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/pkg/compat" "github.com/bangumi/server/internal/pkg/generic/slice" "github.com/bangumi/server/internal/pkg/gstr" + "github.com/bangumi/server/internal/pkg/logger" + "github.com/bangumi/server/internal/pkg/null" + "github.com/bangumi/server/internal/tag" + "github.com/bangumi/server/pkg/vars" ) const defaultShortSummaryLength = 120 -type v0wiki = []any +type V0wiki = []any type SubjectTag struct { - Name string `json:"name"` - Count int `json:"count"` + Name string `json:"name"` + Count uint `json:"count"` + TotalCont uint `json:"total_cont"` } type SubjectV0 struct { Date *string `json:"date"` Platform *string `json:"platform"` - Image SubjectImages `json:"images"` + Images SubjectImages `json:"images"` Summary string `json:"summary"` Name string `json:"name"` NameCN string `json:"name_cn"` Tags []SubjectTag `json:"tags"` - Infobox v0wiki `json:"infobox"` + Infobox V0wiki `json:"infobox"` Rating Rating `json:"rating"` TotalEpisodes int64 `json:"total_episodes" doc:"episodes count in database"` Collection SubjectCollectionStat `json:"collection"` ID model.SubjectID `json:"id"` Eps uint32 `json:"eps"` + MetaTags []string `json:"meta_tags"` Volumes uint32 `json:"volumes"` - Redirect model.SubjectID `json:"-"` + Series bool `json:"series"` Locked bool `json:"locked"` NSFW bool `json:"nsfw"` TypeID model.SubjectType `json:"type"` + Redirect model.SubjectID `json:"-"` } type SlimSubjectV0 struct { @@ -98,6 +108,75 @@ func ToSlimSubjectV0(s model.Subject) SlimSubjectV0 { } } +func PlatformString(s model.Subject) *string { + platform, ok := vars.PlatformMap[s.TypeID][s.PlatformID] + if !ok && s.TypeID != 0 { + logger.Warn("unknown platform", + zap.Uint32("subject", s.ID), + zap.Uint8("type", s.TypeID), + zap.Uint16("platform", s.PlatformID), + ) + + return nil + } + v := platform.String() + return &v +} + +func ToSubjectV0(s model.Subject, totalEpisode int64, metaTags []tag.Tag) SubjectV0 { + return SubjectV0{ + TotalEpisodes: totalEpisode, + ID: s.ID, + Images: SubjectImage(s.Image), + Summary: s.Summary, + Name: s.Name, + Platform: PlatformString(s), + NameCN: s.NameCN, + Date: null.NilString(s.Date), + Infobox: compat.V0Wiki(wiki.ParseOmitError(s.Infobox).NonZero()), + Volumes: s.Volumes, + Redirect: s.Redirect, + Eps: s.Eps, + MetaTags: lo.Map(metaTags, func(item tag.Tag, index int) string { + return item.Name + }), + Tags: slice.Map(s.Tags, func(tag model.Tag) SubjectTag { + return SubjectTag{ + Name: tag.Name, + Count: tag.Count, + } + }), + Collection: SubjectCollectionStat{ + OnHold: s.OnHold, + Wish: s.Wish, + Dropped: s.Dropped, + Collect: s.Collect, + Doing: s.Doing, + }, + TypeID: s.TypeID, + Series: s.Series, + Locked: s.Locked(), + NSFW: s.NSFW, + Rating: Rating{ + Rank: s.Rating.Rank, + Total: s.Rating.Total, + Count: Count{ + Field1: s.Rating.Count.Field1, + Field2: s.Rating.Count.Field2, + Field3: s.Rating.Count.Field3, + Field4: s.Rating.Count.Field4, + Field5: s.Rating.Count.Field5, + Field6: s.Rating.Count.Field6, + Field7: s.Rating.Count.Field7, + Field8: s.Rating.Count.Field8, + Field9: s.Rating.Count.Field9, + Field10: s.Rating.Count.Field10, + }, + Score: s.Rating.Score, + }, + } +} + type SubjectCollectionStat struct { OnHold uint32 `json:"on_hold"` Dropped uint32 `json:"dropped"` @@ -131,11 +210,13 @@ type Rating struct { } type PersonRelatedSubject struct { - Staff string `json:"staff"` - Name string `json:"name"` - NameCn string `json:"name_cn"` - Image string `json:"image"` - SubjectID model.SubjectID `json:"id"` + Staff string `json:"staff"` + Eps string `json:"eps" doc:"episodes participated"` + Name string `json:"name"` + NameCn string `json:"name_cn"` + Image string `json:"image"` + Type model.SubjectType `json:"type"` + SubjectID model.SubjectID `json:"id"` } type PersonRelatedCharacter struct { @@ -143,6 +224,7 @@ type PersonRelatedCharacter struct { Name string `json:"name"` SubjectName string `json:"subject_name"` SubjectNameCn string `json:"subject_name_cn"` + SubjectType model.SubjectType `json:"subject_type"` SubjectID model.SubjectID `json:"subject_id"` Staff string `json:"staff"` ID model.CharacterID `json:"id"` @@ -150,22 +232,24 @@ type PersonRelatedCharacter struct { } type CharacterRelatedPerson struct { - Images PersonImages `json:"images"` - Name string `json:"name"` - SubjectName string `json:"subject_name"` - SubjectNameCn string `json:"subject_name_cn"` - SubjectID model.SubjectID `json:"subject_id"` - Staff string `json:"staff"` - ID model.PersonID `json:"id"` - Type uint8 `json:"type" doc:"person type"` + Images PersonImages `json:"images"` + Name string `json:"name"` + SubjectName string `json:"subject_name"` + SubjectNameCn string `json:"subject_name_cn"` + SubjectType model.SubjectType `json:"subject_type"` + SubjectID model.SubjectID `json:"subject_id"` + Staff string `json:"staff"` + ID model.PersonID `json:"id"` + Type uint8 `json:"type" doc:"person type"` } type CharacterRelatedSubject struct { - Staff string `json:"staff"` - Name string `json:"name"` - NameCn string `json:"name_cn"` - Image string `json:"image"` - ID model.SubjectID `json:"id"` + Staff string `json:"staff"` + Name string `json:"name"` + NameCn string `json:"name_cn"` + Image string `json:"image"` + Type model.SubjectType `json:"type"` + ID model.SubjectID `json:"id"` } type SubjectRelatedSubject struct { @@ -180,6 +264,7 @@ type SubjectRelatedSubject struct { type SubjectRelatedCharacter struct { Images PersonImages `json:"images"` Name string `json:"name"` + Summary string `json:"summary"` Relation string `json:"relation"` Actors []Actor `json:"actors"` Type uint8 `json:"type"` @@ -193,6 +278,7 @@ type SubjectRelatedPerson struct { Career []string `json:"career"` Type uint8 `json:"type"` ID model.PersonID `json:"id" doc:"person ID"` + Eps string `json:"eps" doc:"episodes participated"` } type Actor struct { @@ -212,7 +298,7 @@ type IndexSubjectV0 struct { Name string `json:"name"` NameCN string `json:"name_cn"` Comment string `json:"comment"` - Infobox v0wiki `json:"infobox"` + Infobox V0wiki `json:"infobox"` ID model.SubjectID `json:"id"` TypeID model.SubjectType `json:"type"` } diff --git a/web/routes.go b/web/routes.go index cc81cacc3..0e09d8249 100644 --- a/web/routes.go +++ b/web/routes.go @@ -15,23 +15,17 @@ package web import ( - "fmt" + "github.com/labstack/echo/v5" + "github.com/redis/rueidis" - "github.com/labstack/echo/v4" - - "github.com/bangumi/server/config" "github.com/bangumi/server/web/handler" "github.com/bangumi/server/web/handler/character" "github.com/bangumi/server/web/handler/common" "github.com/bangumi/server/web/handler/index" - "github.com/bangumi/server/web/handler/notification" "github.com/bangumi/server/web/handler/person" - "github.com/bangumi/server/web/handler/pm" "github.com/bangumi/server/web/handler/subject" "github.com/bangumi/server/web/handler/user" "github.com/bangumi/server/web/mw" - "github.com/bangumi/server/web/mw/origin" - "github.com/bangumi/server/web/mw/referer" "github.com/bangumi/server/web/mw/ua" "github.com/bangumi/server/web/req" ) @@ -41,24 +35,26 @@ import ( //nolint:funlen func AddRouters( app *echo.Echo, - c config.AppConfig, common common.Common, + rueidis rueidis.Client, h handler.Handler, userHandler user.User, personHandler person.Person, characterHandler character.Character, - pmHandler pm.PrivateMessage, - notificationHandler notification.Notification, subjectHandler subject.Subject, indexHandler index.Handler, ) { app.GET("/", indexPage()) app.Use(ua.DisableDefaultHTTPLibrary) + app.Use(ua.DisableBrokenUA) v0 := app.Group("/v0", common.MiddlewareAccessTokenAuth) + v0.Use(mw.RateLimit(common.Config, rueidis)) - v0.POST("/search/subjects", h.Search) + v0.POST("/search/subjects", h.SearchSubjects) + v0.POST("/search/characters", h.SearchCharacters) + v0.POST("/search/persons", h.SearchPersons) subjectHandler.Routes(v0) @@ -66,26 +62,40 @@ func AddRouters( v0.GET("/persons/:id/image", personHandler.GetImage) v0.GET("/persons/:id/subjects", personHandler.GetRelatedSubjects) v0.GET("/persons/:id/characters", personHandler.GetRelatedCharacters) + v0.POST("/persons/:id/collect", personHandler.CollectPerson, mw.NeedLogin) + // TODO: wait for soft delete + // v0.DELETE("/persons/:id/collect", personHandler.UncollectPerson, mw.NeedLogin) + v0.GET("/characters/:id", characterHandler.Get) v0.GET("/characters/:id/image", characterHandler.GetImage) v0.GET("/characters/:id/subjects", characterHandler.GetRelatedSubjects) v0.GET("/characters/:id/persons", characterHandler.GetRelatedPersons) + v0.POST("/characters/:id/collect", characterHandler.CollectCharacter, mw.NeedLogin) + // TODO: wait for soft delete + // v0.DELETE("/characters/:id/collect", characterHandler.UncollectCharacter, mw.NeedLogin) + v0.GET("/episodes/:id", h.GetEpisode) v0.GET("/episodes", h.ListEpisode) // echo 中间件从前往后运行按顺序 v0.GET("/me", userHandler.GetCurrent) v0.GET("/users/:username", userHandler.Get) + v0.GET("/users/:username/avatar", userHandler.GetAvatar) v0.GET("/users/:username/collections", userHandler.ListSubjectCollection) v0.GET("/users/:username/collections/:subject_id", userHandler.GetSubjectCollection) + v0.GET("/users/-/collections/-/episodes/:episode_id", userHandler.GetEpisodeCollection, mw.NeedLogin) v0.PUT("/users/-/collections/-/episodes/:episode_id", userHandler.PutEpisodeCollection, req.JSON, mw.NeedLogin) v0.GET("/users/-/collections/:subject_id/episodes", userHandler.GetSubjectEpisodeCollection, mw.NeedLogin) v0.PATCH("/users/-/collections/:subject_id", userHandler.PatchSubjectCollection, req.JSON, mw.NeedLogin) + v0.POST("/users/-/collections/:subject_id", userHandler.PostSubjectCollection, req.JSON, mw.NeedLogin) v0.PATCH("/users/-/collections/:subject_id/episodes", userHandler.PatchEpisodeCollectionBatch, req.JSON, mw.NeedLogin) - v0.GET("/users/:username/avatar", userHandler.GetAvatar) + v0.GET("/users/:username/collections/-/characters", userHandler.ListCharacterCollection) + v0.GET("/users/:username/collections/-/characters/:character_id", userHandler.GetCharacterCollection) + v0.GET("/users/:username/collections/-/persons", userHandler.ListPersonCollection) + v0.GET("/users/:username/collections/-/persons/:person_id", userHandler.GetPersonCollection) { i := indexHandler @@ -114,29 +124,6 @@ func AddRouters( v0.GET("/revisions/episodes", h.ListEpisodeRevision) v0.Any("/*", globalNotFoundHandler) - var originMiddleware = origin.New(fmt.Sprintf("https://%s", c.WebDomain)) - var refererMiddleware = referer.New(fmt.Sprintf("https://%s/", c.WebDomain)) - - var CORSBlockMiddleware []echo.MiddlewareFunc - if c.WebDomain != "" { - CORSBlockMiddleware = []echo.MiddlewareFunc{originMiddleware, refererMiddleware} - } - - // frontend private api - private := app.Group("/p", append(CORSBlockMiddleware, common.MiddlewareSessionAuth)...) - - // TODO migrate this to bangumi/graphql - private.GET("/pms/list", pmHandler.List, mw.NeedLogin) - private.GET("/pms/related-msgs/:id", pmHandler.ListRelated, mw.NeedLogin) - private.GET("/pms/counts", pmHandler.CountTypes, mw.NeedLogin) - private.GET("/pms/contacts/recent", pmHandler.ListRecentContact, mw.NeedLogin) - private.PATCH("/pms/read", pmHandler.MarkRead) - private.POST("/pms", pmHandler.Create) - private.DELETE("/pms", pmHandler.Delete, req.JSON, mw.NeedLogin) - - private.GET("/notifications/count", notificationHandler.Count, mw.NeedLogin) - private.Any("/*", globalNotFoundHandler) - // default 404 Handler, all router should be added before this router app.Any("/*", globalNotFoundHandler) } diff --git a/web/session/manager.go b/web/session/manager.go index 2baa71d3d..1fc22aceb 100644 --- a/web/session/manager.go +++ b/web/session/manager.go @@ -25,7 +25,6 @@ import ( "github.com/bangumi/server/internal/auth" "github.com/bangumi/server/internal/model" "github.com/bangumi/server/internal/pkg/cache" - "github.com/bangumi/server/internal/pkg/generic" "github.com/bangumi/server/internal/pkg/gtime" "github.com/bangumi/server/internal/pkg/random" ) @@ -93,7 +92,7 @@ func (m manager) Get(ctx context.Context, key string) (Session, error) { } // 缓存3天或缓存者到token失效 - ttl := generic.Min(gtime.OneDaySec*3, s.ExpiredAt) + ttl := min(gtime.OneDaySec*3, s.ExpiredAt) if err := m.cache.Set(ctx, redisKeyPrefix+key, s, gtime.Second(ttl)); err != nil { m.log.Panic("failed to set cache") diff --git a/web/util/detail.go b/web/util/detail.go index d684dfdf3..41b978961 100644 --- a/web/util/detail.go +++ b/web/util/detail.go @@ -15,10 +15,10 @@ package util import ( - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" ) -func DetailWithErr(c echo.Context, err error) D { +func DetailWithErr(c *echo.Context, err error) D { return D{ Path: c.Request().URL.Path, Error: err.Error(), @@ -27,7 +27,7 @@ func DetailWithErr(c echo.Context, err error) D { } } -func Detail(c echo.Context) D { +func Detail(c *echo.Context) D { return D{ Path: c.Request().URL.Path, Method: c.Request().Method, diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 000000000..f487830e9 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,280 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@apidevtools/json-schema-ref-parser@^15.3.5": + version "15.3.5" + resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-15.3.5.tgz#503726178d8d792eea7b195566272aaee6837e2f" + integrity sha512-orNOYXw3hYXxxisXMldjzjBzqqTLBPbwOtHg7ovBPvfBHDue1qM9YJENZ3W2BQuS+7z4ThogMbEzEsov57Itkg== + dependencies: + js-yaml "^4.1.1" + +"@exodus/schemasafe@^1.0.0-rc.2": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@exodus/schemasafe/-/schemasafe-1.3.0.tgz#731656abe21e8e769a7f70a4d833e6312fe59b7f" + integrity sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw== + +"@types/json-schema@^7.0.15": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +call-me-maybe@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.2.tgz#03f964f19522ba643b1b0693acb9152fe2074baa" + integrity sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colors@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +escalade@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +fast-safe-stringify@^2.0.7: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +http2-client@^1.2.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/http2-client/-/http2-client-1.3.5.tgz#20c9dc909e3cc98284dd20af2432c524086df181" + integrity sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +js-yaml@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== + dependencies: + argparse "^2.0.1" + +lodash@^4.18.1: + version "4.18.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" + integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== + +node-fetch-h2@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz#c6188325f9bd3d834020bf0f2d6dc17ced2241ac" + integrity sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg== + dependencies: + http2-client "^1.2.5" + +oas-kit-common@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/oas-kit-common/-/oas-kit-common-1.0.8.tgz#6d8cacf6e9097967a4c7ea8bcbcbd77018e1f535" + integrity sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ== + dependencies: + fast-safe-stringify "^2.0.7" + +oas-linter@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/oas-linter/-/oas-linter-3.2.2.tgz#ab6a33736313490659035ca6802dc4b35d48aa1e" + integrity sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ== + dependencies: + "@exodus/schemasafe" "^1.0.0-rc.2" + should "^13.2.1" + yaml "^1.10.0" + +oas-resolver@^2.5.6: + version "2.5.6" + resolved "https://registry.yarnpkg.com/oas-resolver/-/oas-resolver-2.5.6.tgz#10430569cb7daca56115c915e611ebc5515c561b" + integrity sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ== + dependencies: + node-fetch-h2 "^2.3.0" + oas-kit-common "^1.0.8" + reftools "^1.1.9" + yaml "^1.10.0" + yargs "^17.0.1" + +oas-schema-walker@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz#74c3cd47b70ff8e0b19adada14455b5d3ac38a22" + integrity sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ== + +oas-validator@^5.0.8: + version "5.0.8" + resolved "https://registry.yarnpkg.com/oas-validator/-/oas-validator-5.0.8.tgz#387e90df7cafa2d3ffc83b5fb976052b87e73c28" + integrity sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw== + dependencies: + call-me-maybe "^1.0.1" + oas-kit-common "^1.0.8" + oas-linter "^3.2.2" + oas-resolver "^2.5.6" + oas-schema-walker "^1.1.5" + reftools "^1.1.9" + should "^13.2.1" + yaml "^1.10.0" + +prettier@^3.8.3: + version "3.8.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.3.tgz#560f2de55bf01b4c0503bc629d5df99b9a1d09b0" + integrity sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw== + +reftools@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/reftools/-/reftools-1.1.9.tgz#e16e19f662ccd4648605312c06d34e5da3a2b77e" + integrity sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +should-equal@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3" + integrity sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA== + dependencies: + should-type "^1.4.0" + +should-format@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/should-format/-/should-format-3.0.3.tgz#9bfc8f74fa39205c53d38c34d717303e277124f1" + integrity sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q== + dependencies: + should-type "^1.3.0" + should-type-adaptors "^1.0.1" + +should-type-adaptors@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz#401e7f33b5533033944d5cd8bf2b65027792e27a" + integrity sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA== + dependencies: + should-type "^1.3.0" + should-util "^1.0.0" + +should-type@^1.3.0, should-type@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/should-type/-/should-type-1.4.0.tgz#0756d8ce846dfd09843a6947719dfa0d4cff5cf3" + integrity sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ== + +should-util@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/should-util/-/should-util-1.0.1.tgz#fb0d71338f532a3a149213639e2d32cbea8bcb28" + integrity sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g== + +should@^13.2.1: + version "13.2.3" + resolved "https://registry.yarnpkg.com/should/-/should-13.2.3.tgz#96d8e5acf3e97b49d89b51feaa5ae8d07ef58f10" + integrity sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ== + dependencies: + should-equal "^2.0.0" + should-format "^3.0.3" + should-type "^1.4.0" + should-type-adaptors "^1.0.1" + should-util "^1.0.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yaml@^1.10.0: + version "1.10.3" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.3.tgz#76e407ed95c42684fb8e14641e5de62fe65bbcb3" + integrity sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA== + +yaml@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.9.0.tgz#78274afd93598a1dfdd6130df6a566defcbf9aa4" + integrity sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.0.1: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1"