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 @@
新后端服务器。
-
-[](https://app.codecov.io/gh/Bangumi/server)
-
## Requirements
-- [Go 1.19](https://go.dev/)
+- 
- [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"