Compare commits
68
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d25beede91 | ||
|
|
16b690c8c0 | ||
|
|
2036a0261e | ||
|
|
66c8f6b94b | ||
|
|
1bf21404bd | ||
|
|
9ec0f1c74a | ||
|
|
9b5e47d602 | ||
|
|
03a433d94a | ||
|
|
4a9b07e687 | ||
|
|
d00b63c0af | ||
|
|
97352b583e | ||
|
|
0b44989e5f | ||
|
|
a5308548ba | ||
|
|
9225fc32f8 | ||
|
|
37dc6a68d7 | ||
|
|
69613beb88 | ||
|
|
9c5bd9653f | ||
|
|
ca3d37fc2d | ||
|
|
f554e6c438 | ||
|
|
76066bbdc0 | ||
|
|
3950ef8532 | ||
|
|
3f6eb935e4 | ||
|
|
5364d191c7 | ||
|
|
5e1b04326b | ||
|
|
add379c82c | ||
|
|
d7565e5b33 | ||
|
|
d46326788b | ||
|
|
729fd9880a | ||
|
|
5b6a453349 | ||
|
|
b7b6700407 | ||
|
|
cb2860a778 | ||
|
|
705e220a81 | ||
|
|
ef2d1e8552 | ||
|
|
14e2d9e824 | ||
|
|
9dba2ae43f | ||
|
|
f1784a1bd0 | ||
|
|
bc30c4d26d | ||
|
|
ad9abd643c | ||
|
|
2e7a34fc9c | ||
|
|
082e9b7fed | ||
|
|
a95c1b9be6 | ||
|
|
dfe65ce621 | ||
|
|
da6556ec3c | ||
|
|
9fe237660c | ||
|
|
eff4a0b15d | ||
|
|
5b0e4c866e | ||
|
|
617de2cdf1 | ||
|
|
36c47104ca | ||
|
|
8460ae387f | ||
|
|
e6a5c2d4b9 | ||
|
|
1cd9cdb445 | ||
|
|
117908e57b | ||
|
|
b47eed5303 | ||
|
|
f7a2aeac9b | ||
|
|
5ff42483aa | ||
|
|
8de7157a91 | ||
|
|
747b184fa4 | ||
|
|
f875fc4023 | ||
|
|
cf06808170 | ||
|
|
3b00a5e356 | ||
|
|
3d9c484a5d | ||
|
|
dcac8cc883 | ||
|
|
b39ec2a0a6 | ||
|
|
5c3df4776e | ||
|
|
6ba19150df | ||
|
|
efe62e93e2 | ||
|
|
71a651153b | ||
|
|
b4c139bf9e |
No files matched your search
@@ -1 +1,14 @@
|
||||
node_modules
|
||||
.git
|
||||
.github
|
||||
.DS_Store
|
||||
.vscode
|
||||
bin
|
||||
docs/docs/*
|
||||
!docs/docs/package.json
|
||||
!docs/docs/tsconfig.json
|
||||
local
|
||||
test
|
||||
ui/**/dist
|
||||
**/playwright-report
|
||||
**/test-results
|
||||
@@ -12,13 +12,19 @@ inputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
ISSUE_NUMBER: ${{ inputs.issue_number }}
|
||||
STDOUT: ${{ inputs.content }}
|
||||
with:
|
||||
script: |
|
||||
const issueNumber = Number.parseInt(process.env.ISSUE_NUMBER, 10)
|
||||
if (!Number.isSafeInteger(issueNumber) || issueNumber <= 0) {
|
||||
throw new Error('Invalid issue number')
|
||||
}
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: ${{ inputs.issue_number }},
|
||||
issue_number: issueNumber,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: process.env.STDOUT
|
||||
|
||||
@@ -9,6 +9,8 @@ updates:
|
||||
directory: /
|
||||
schedule:
|
||||
interval: monthly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
commit-message:
|
||||
prefix: "chore(deps/go): "
|
||||
groups:
|
||||
@@ -23,6 +25,8 @@ updates:
|
||||
directory: /
|
||||
schedule:
|
||||
interval: monthly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
commit-message:
|
||||
prefix: "chore(deps/ui): "
|
||||
groups:
|
||||
@@ -37,6 +41,8 @@ updates:
|
||||
directory: /
|
||||
schedule:
|
||||
interval: monthly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
commit-message:
|
||||
prefix: "chore(deps/docker): "
|
||||
groups:
|
||||
@@ -49,6 +55,8 @@ updates:
|
||||
directory: /.github/workflows
|
||||
schedule:
|
||||
interval: monthly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
commit-message:
|
||||
prefix: "chore(deps/ci): "
|
||||
groups:
|
||||
|
||||
@@ -6,14 +6,15 @@ on:
|
||||
version:
|
||||
required: true
|
||||
type: string
|
||||
ref:
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
required: true
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.22.7
|
||||
DOCKER_IMG: ghcr.io/goreleaser/goreleaser-cross
|
||||
CACHE_DIR: /tmp/cache/docker-image
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build_publish:
|
||||
@@ -21,64 +22,83 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.version }}
|
||||
ref: ${{ inputs.ref }}
|
||||
persist-credentials: false
|
||||
# `go-releaser` will read the semver version from git tag name
|
||||
# `git-chglog` will diff the tag and generate changelog
|
||||
fetch-depth: 0
|
||||
|
||||
# disable cache because the err `no space left on device`
|
||||
# - name: Docker Image Cache
|
||||
# id: docker-cache
|
||||
# uses: actions/cache@v3
|
||||
# with:
|
||||
# path: ${{ env.CACHE_DIR }}
|
||||
# key: docker-image-go-cross-${{ env.GO_VERSION }}
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
# Release inputs are verified from the lock files instead of shared caches.
|
||||
cache: false
|
||||
|
||||
- name: Pull Docker Image
|
||||
# if: steps.docker-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
docker pull "${DOCKER_IMG}:v${GO_VERSION}"
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: 10.33.2
|
||||
|
||||
# mkdir -p "${CACHE_DIR}"
|
||||
# docker save -o "${CACHE_DIR}/go-cross.tar" "${DOCKER_IMG}:v${GO_VERSION}"
|
||||
|
||||
# - name: Restore Docker Image from Cache
|
||||
# if: steps.docker-cache.outputs.cache-hit == 'true'
|
||||
# run: docker load -i ${CACHE_DIR}/go-cross.tar
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 22.x
|
||||
package-manager-cache: false
|
||||
|
||||
- name: Setup git-chglog
|
||||
run: |
|
||||
curl -sL $(curl -s https://api.github.com/repos/git-chglog/git-chglog/releases/latest \
|
||||
| grep -oP '"https://.+linux_amd64.tar.gz"' | tr -d \") | tar -C /usr/local/bin -xz git-chglog
|
||||
git-chglog --version
|
||||
GOBIN="$RUNNER_TEMP/bin" go install github.com/git-chglog/git-chglog/cmd/git-chglog@v0.15.4
|
||||
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
|
||||
go version -m "$RUNNER_TEMP/bin/git-chglog"
|
||||
|
||||
- name: Create local tag for dry run
|
||||
if: ${{ inputs.dry_run }}
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
if git rev-parse --quiet --verify "refs/tags/$VERSION" > /dev/null; then
|
||||
tag_commit="$(git rev-list -n 1 "$VERSION")"
|
||||
head_commit="$(git rev-parse HEAD)"
|
||||
if [[ "$tag_commit" != "$head_commit" ]]; then
|
||||
echo "Tag $VERSION already points to $tag_commit, expected $head_commit."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
git tag "$VERSION"
|
||||
fi
|
||||
|
||||
- name: Pre Build
|
||||
run: |-
|
||||
mkdir -p local
|
||||
|
||||
# github token
|
||||
echo "GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}" > local/.release-env
|
||||
|
||||
# changelog
|
||||
git-chglog --config .github/chglog/config.yml ${{ inputs.version }} > local/release-notes.md
|
||||
git-chglog --config .github/chglog/config.yml "$VERSION" > local/release-notes.md
|
||||
|
||||
# copy config file
|
||||
cp conf/artalk.example.yml artalk.yml
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
|
||||
- name: Print Relase Notes
|
||||
- name: Print Release Notes
|
||||
run: cat local/release-notes.md
|
||||
|
||||
- name: Validate release build
|
||||
if: ${{ inputs.dry_run }}
|
||||
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: v2.17.0
|
||||
args: release --clean --release-notes local/release-notes.md --skip=publish
|
||||
|
||||
- name: Build and Release
|
||||
run: |
|
||||
docker run \
|
||||
--rm \
|
||||
--privileged \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v $(pwd)/sysroot:/sysroot \
|
||||
-v $(pwd):/repo \
|
||||
-w /repo \
|
||||
--env-file local/.release-env \
|
||||
ghcr.io/goreleaser/goreleaser-cross:v${GO_VERSION} \
|
||||
release --release-notes local/release-notes.md ${{ inputs.dry_run && '--skip=publish' || '' }}
|
||||
if: ${{ !inputs.dry_run }}
|
||||
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: v2.17.0
|
||||
args: release --clean --release-notes local/release-notes.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
@@ -6,77 +6,283 @@ on:
|
||||
version:
|
||||
required: true
|
||||
type: string
|
||||
ref:
|
||||
required: true
|
||||
type: string
|
||||
commit:
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
required: true
|
||||
type: boolean
|
||||
target:
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME:
|
||||
required: false
|
||||
DOCKERHUB_TOKEN:
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
DIGEST_DIR: /tmp/digests
|
||||
DOCKER_IMG: artalk/artalk-go
|
||||
|
||||
jobs:
|
||||
build_publish:
|
||||
runs-on: ubuntu-latest
|
||||
ui:
|
||||
name: Build Docker UI assets
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Verify release source
|
||||
env:
|
||||
RELEASE_COMMIT: ${{ inputs.commit }}
|
||||
run: |
|
||||
if [[ ! "$RELEASE_COMMIT" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
echo "Invalid release commit: $RELEASE_COMMIT"
|
||||
exit 1
|
||||
fi
|
||||
checkout_commit="$(git rev-parse HEAD)"
|
||||
if [[ "$checkout_commit" != "$RELEASE_COMMIT" ]]; then
|
||||
echo "Checked out $checkout_commit, expected $RELEASE_COMMIT."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: 10.33.2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 22.x
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build UI
|
||||
run: make build-frontend
|
||||
|
||||
- name: Upload UI assets
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: docker-ui-src
|
||||
path: |
|
||||
public
|
||||
ui/artalk/dist
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
build:
|
||||
name: Build Docker (${{ matrix.platform }})
|
||||
needs: ui
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: ubuntu-24.04
|
||||
platform: linux/amd64
|
||||
platform_pair: linux_amd64
|
||||
- runner: ubuntu-24.04-arm
|
||||
platform: linux/arm64
|
||||
platform_pair: linux_arm64
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.version }}
|
||||
ref: ${{ inputs.ref }}
|
||||
persist-credentials: false
|
||||
|
||||
# https://github.com/docker/metadata-action
|
||||
- name: Gen docker meta
|
||||
- name: Verify release source
|
||||
env:
|
||||
RELEASE_COMMIT: ${{ inputs.commit }}
|
||||
run: |
|
||||
if [[ ! "$RELEASE_COMMIT" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
echo "Invalid release commit: $RELEASE_COMMIT"
|
||||
exit 1
|
||||
fi
|
||||
checkout_commit="$(git rev-parse HEAD)"
|
||||
if [[ "$checkout_commit" != "$RELEASE_COMMIT" ]]; then
|
||||
echo "Checked out $checkout_commit, expected $RELEASE_COMMIT."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Download UI assets
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: docker-ui-src
|
||||
|
||||
- name: Generate Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
||||
with:
|
||||
context: git
|
||||
images: |
|
||||
${{ env.DOCKER_IMG }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha
|
||||
images: ${{ env.DOCKER_IMG }}
|
||||
tags: type=raw,value=build-${{ matrix.platform_pair }}
|
||||
|
||||
# https://github.com/docker/login-action
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
if: ${{ !inputs.dry_run }}
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
# https://github.com/docker/setup-qemu-action
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: ${{ inputs.target }}
|
||||
|
||||
# https://github.com/docker/setup-buildx-action
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
# Build UI outside of Docker to speed up cross-platform builds
|
||||
- name: Build UI
|
||||
run: |
|
||||
make build-frontend
|
||||
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and Push
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
- name: Build dry-run image
|
||||
if: ${{ inputs.dry_run }}
|
||||
id: build_dry_run
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
with:
|
||||
push: ${{ !inputs.dry_run }}
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
build-args: |
|
||||
APP_VERSION=${{ inputs.version }}
|
||||
APP_COMMIT_HASH=${{ inputs.commit }}
|
||||
SKIP_UI_BUILD=true
|
||||
platforms: ${{ inputs.target }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=docker
|
||||
cache-from: type=gha,scope=release-${{ matrix.platform_pair }}
|
||||
cache-to: type=gha,mode=max,scope=release-${{ matrix.platform_pair }}
|
||||
|
||||
- name: Print image digest
|
||||
run: echo ${{ steps.build.outputs.digest }}
|
||||
- name: Build and push image by digest
|
||||
if: ${{ !inputs.dry_run }}
|
||||
id: build
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
APP_VERSION=${{ inputs.version }}
|
||||
APP_COMMIT_HASH=${{ inputs.commit }}
|
||||
SKIP_UI_BUILD=true
|
||||
outputs: type=image,push=true,name=${{ env.DOCKER_IMG }},push-by-digest=true,name-canonical=true
|
||||
cache-from: type=gha,scope=release-${{ matrix.platform_pair }}
|
||||
cache-to: type=gha,mode=max,scope=release-${{ matrix.platform_pair }}
|
||||
provenance: mode=max
|
||||
sbom: true
|
||||
|
||||
- name: Smoke test dry-run image
|
||||
if: ${{ inputs.dry_run }}
|
||||
env:
|
||||
IMAGE: ${{ steps.meta.outputs.tags }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
output="$(docker run --rm --entrypoint /artalk "$IMAGE" version)"
|
||||
grep --fixed-strings "Artalk ($VERSION/" <<< "$output"
|
||||
|
||||
- name: Smoke test published image
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
IMAGE: ${{ env.DOCKER_IMG }}@${{ steps.build.outputs.digest }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
docker pull "$IMAGE"
|
||||
output="$(docker run --rm --entrypoint /artalk "$IMAGE" version)"
|
||||
grep --fixed-strings "Artalk ($VERSION/" <<< "$output"
|
||||
|
||||
- name: Export digest
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
DIGEST: ${{ steps.build.outputs.digest }}
|
||||
run: |
|
||||
mkdir -p "$DIGEST_DIR"
|
||||
touch "$DIGEST_DIR/${DIGEST#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
if: ${{ !inputs.dry_run }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: release-digest-${{ matrix.platform_pair }}
|
||||
path: ${{ env.DIGEST_DIR }}/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
merge:
|
||||
name: Publish Docker manifest
|
||||
if: ${{ !inputs.dry_run }}
|
||||
needs: build
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: ${{ env.DIGEST_DIR }}
|
||||
pattern: release-digest-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: Prepare release metadata
|
||||
env:
|
||||
RELEASE_COMMIT: ${{ inputs.commit }}
|
||||
run: |
|
||||
if [[ ! "$RELEASE_COMMIT" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
echo "Invalid release commit: $RELEASE_COMMIT"
|
||||
exit 1
|
||||
fi
|
||||
echo "RELEASE_COMMIT_SHORT=${RELEASE_COMMIT:0:7}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Generate Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
||||
with:
|
||||
images: ${{ env.DOCKER_IMG }}
|
||||
tags: |
|
||||
type=semver,pattern={{version}},value=${{ inputs.version }}
|
||||
type=semver,pattern={{major}}.{{minor}},value=${{ inputs.version }}
|
||||
type=raw,value=sha-${{ env.RELEASE_COMMIT_SHORT }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Create and push manifest
|
||||
env:
|
||||
METADATA: ${{ steps.meta.outputs.json }}
|
||||
run: |
|
||||
mapfile -t tags < <(jq -r '.tags[]' <<< "$METADATA")
|
||||
tag_args=()
|
||||
for tag in "${tags[@]}"; do
|
||||
tag_args+=(--tag "$tag")
|
||||
done
|
||||
|
||||
shopt -s nullglob
|
||||
digest_files=("$DIGEST_DIR"/*)
|
||||
if [[ "${#digest_files[@]}" -ne 2 ]]; then
|
||||
echo "Expected 2 platform digests, found ${#digest_files[@]}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sources=()
|
||||
for digest_file in "${digest_files[@]}"; do
|
||||
digest="$(basename "$digest_file")"
|
||||
if [[ ! "$digest" =~ ^[0-9a-f]{64}$ ]]; then
|
||||
echo "Invalid image digest: $digest"
|
||||
exit 1
|
||||
fi
|
||||
sources+=("$DOCKER_IMG@sha256:$digest")
|
||||
done
|
||||
|
||||
docker buildx imagetools create "${tag_args[@]}" "${sources[@]}"
|
||||
|
||||
- name: Inspect image
|
||||
env:
|
||||
IMAGE: ${{ env.DOCKER_IMG }}:${{ steps.meta.outputs.version }}
|
||||
run: docker buildx imagetools inspect "$IMAGE"
|
||||
@@ -6,6 +6,9 @@ on:
|
||||
version:
|
||||
required: true
|
||||
type: string
|
||||
ref:
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
required: true
|
||||
type: boolean
|
||||
@@ -13,18 +16,24 @@ on:
|
||||
required: true
|
||||
type: number
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
done:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.version }}
|
||||
ref: ${{ inputs.ref }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Comment done info
|
||||
uses: ./.github/actions/comment
|
||||
if: ${{ inputs.issue_number != 0 }}
|
||||
if: ${{ !inputs.dry_run && inputs.issue_number != 0 }}
|
||||
with:
|
||||
issue_number: ${{ inputs.issue_number }}
|
||||
content: |-
|
||||
|
||||
+216
-188
@@ -1,5 +1,5 @@
|
||||
name: Build Nightly
|
||||
run-name: Build Nightly
|
||||
run-name: Build Nightly ${{ inputs.dry_run && '(🧪 Dry-Run)' || '' }}
|
||||
|
||||
on:
|
||||
schedule:
|
||||
@@ -7,72 +7,64 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry_run:
|
||||
description: "Dry run"
|
||||
description: "Build without publishing"
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: build-nightly
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
DIST_DIR: /tmp/builds
|
||||
DIGEST_DIR: /tmp/digests
|
||||
DOCKER_IMG: artalk/artalk-go
|
||||
DOCKER_BUILD_ARGS: |-
|
||||
SKIP_UI_BUILD=true
|
||||
|
||||
jobs:
|
||||
# Prepare check should run
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
skip: ${{ steps.check.outputs.skip }}
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Check should run
|
||||
- name: Check whether the scheduled build should run
|
||||
id: check
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
if [[ "$(git log --since='24 hours ago' | wc -l)" -eq 0 ]] || \
|
||||
if [[ "$(git log --since='24 hours ago' --oneline | wc -l)" -eq 0 ]] || \
|
||||
[[ "$GITHUB_REPOSITORY" != "ArtalkJS/Artalk" ]]; then
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# Bulid UI first
|
||||
# (build ui outside of docker to speed up cross-platform builds)
|
||||
ui:
|
||||
name: Build Nightly UI assets
|
||||
if: ${{ needs.prepare.outputs.skip != 'true' }}
|
||||
needs: prepare
|
||||
runs-on: macos-14
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: 9.10.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "PNPM_STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v4
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.PNPM_STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
node-version: 22.x
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
@@ -81,273 +73,309 @@ jobs:
|
||||
id: build_ui
|
||||
run: |
|
||||
make build-frontend
|
||||
echo "ui_tarball=$(make -s pack-frontend)" >> $GITHUB_OUTPUT
|
||||
echo "ui_tarball=$(make -s pack-frontend)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload UI dist
|
||||
uses: actions/upload-artifact@v4
|
||||
- name: Upload UI assets
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ui-src
|
||||
name: nightly-ui-src
|
||||
path: |
|
||||
public
|
||||
ui/artalk/dist
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload UI tarball (included in release)
|
||||
uses: actions/upload-artifact@v4
|
||||
- name: Upload UI tarball
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: build-ui-tarball
|
||||
path: ${{ steps.build_ui.outputs.ui_tarball }}
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
#
|
||||
# Build app on different platforms
|
||||
#
|
||||
build:
|
||||
name: Build Nightly (${{ matrix.platform }})
|
||||
needs: ui
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
# - macos-14
|
||||
# :(
|
||||
# native Apple m1 arm64 is not supported nested virtualization yet, so docker qemu is not working.
|
||||
# https://github.com/actions/runner-images/issues/9460
|
||||
# https://docs.veertu.com/anka/anka-virtualization-cli/nested-virtualization/
|
||||
#
|
||||
# wait for arm64 linux runner support on github actions.
|
||||
platform:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
# exclude:
|
||||
# # exclude build x86 on arm, and vice versa.
|
||||
# - { os: macos-14, platform: linux/amd64 }
|
||||
# - { os: ubuntu-latest, platform: linux/arm64 }
|
||||
include:
|
||||
- runner: ubuntu-24.04
|
||||
platform: linux/amd64
|
||||
platform_pair: linux_amd64
|
||||
- runner: ubuntu-24.04-arm
|
||||
platform: linux/arm64
|
||||
platform_pair: linux_arm64
|
||||
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
PF="${{ matrix.platform }}"
|
||||
echo "PLATFORM_PAIR=${PF//\//_}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup docker on macos
|
||||
if: startsWith(matrix.os, 'macos')
|
||||
uses: crazy-max/ghaction-setup-docker@v3
|
||||
- name: Download UI assets
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: nightly-ui-src
|
||||
|
||||
- name: Gen docker meta
|
||||
- name: Generate Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
||||
with:
|
||||
images: ${{ env.DOCKER_IMG }}
|
||||
tags: type=raw,value=nightly
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
tags: type=raw,value=nightly-build-${{ matrix.platform_pair }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
if: ${{ github.event_name == 'schedule' || !inputs.dry_run }}
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Download UI before build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ui-src
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: Build and push by digest
|
||||
uses: docker/build-push-action@v6
|
||||
id: build
|
||||
- name: Build dry-run image
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }}
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: ${{ env.DOCKER_BUILD_ARGS }}
|
||||
# The `type=image,push=true` must the last one,
|
||||
# because the `build.outputs.digest` will output the last image's digest.
|
||||
# The digests will be used in the merge step.
|
||||
build-args: |
|
||||
APP_COMMIT_HASH=${{ github.sha }}
|
||||
SKIP_UI_BUILD=true
|
||||
outputs: type=docker,name=${{ steps.meta.outputs.tags }}
|
||||
cache-from: type=gha,scope=nightly-${{ matrix.platform_pair }}
|
||||
cache-to: type=gha,mode=max,scope=nightly-${{ matrix.platform_pair }}
|
||||
|
||||
- name: Build and push image by digest
|
||||
if: ${{ github.event_name == 'schedule' || !inputs.dry_run }}
|
||||
id: build
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
APP_COMMIT_HASH=${{ github.sha }}
|
||||
SKIP_UI_BUILD=true
|
||||
outputs: |
|
||||
type=docker,name=${{ env.DOCKER_IMG }}
|
||||
type=docker,name=${{ steps.meta.outputs.tags }}
|
||||
type=image,push=true,name=${{ env.DOCKER_IMG }},push-by-digest=true,name-canonical=true
|
||||
cache-from: type=gha,scope=nightly-${{ matrix.platform_pair }}
|
||||
cache-to: type=gha,mode=max,scope=nightly-${{ matrix.platform_pair }}
|
||||
|
||||
- name: Smoke test image
|
||||
env:
|
||||
IMAGE: ${{ steps.meta.outputs.tags }}
|
||||
run: docker run --rm --entrypoint /artalk "$IMAGE" version
|
||||
|
||||
- name: Export digest
|
||||
if: ${{ github.event_name == 'schedule' || !inputs.dry_run }}
|
||||
env:
|
||||
DIGEST: ${{ steps.build.outputs.digest }}
|
||||
run: |
|
||||
mkdir -p $DIGEST_DIR
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "$DIGEST_DIR/${digest#sha256:}"
|
||||
mkdir -p "$DIGEST_DIR"
|
||||
touch "$DIGEST_DIR/${DIGEST#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ github.event_name == 'schedule' || !inputs.dry_run }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
name: nightly-digest-${{ matrix.platform_pair }}
|
||||
path: ${{ env.DIGEST_DIR }}/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
# Export docker image
|
||||
- name: Export Docker Image
|
||||
- name: Export Docker image
|
||||
id: docker
|
||||
env:
|
||||
IMAGE: ${{ steps.meta.outputs.tags }}
|
||||
PLATFORM_PAIR: ${{ matrix.platform_pair }}
|
||||
run: |
|
||||
IMG_FILE="artalk_docker_nigthly_$(date +'%Y%m%d')_${PLATFORM_PAIR}.tar.gz"
|
||||
docker save ${{ env.DOCKER_IMG }} | gzip > $IMG_FILE
|
||||
echo "img_file=$IMG_FILE" >> $GITHUB_OUTPUT
|
||||
image_file="artalk_docker_nigthly_$(date +'%Y%m%d')_${PLATFORM_PAIR}.tar.gz"
|
||||
docker save "$IMAGE" | gzip > "$image_file"
|
||||
echo "image_file=$image_file" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# App
|
||||
- name: Pack app
|
||||
id: app
|
||||
env:
|
||||
IMAGE: ${{ steps.meta.outputs.tags }}
|
||||
PLATFORM_PAIR: ${{ matrix.platform_pair }}
|
||||
run: |
|
||||
BASE_DIR="/tmp/app_pkg"
|
||||
NAME="artalk_nigthly_$(date +'%Y%m%d')_${PLATFORM_PAIR}"
|
||||
SUB_DIR="$BASE_DIR/$NAME"
|
||||
PKG_FILE="$BASE_DIR/$NAME.tar.gz"
|
||||
base_dir="/tmp/app_pkg"
|
||||
name="artalk_nigthly_$(date +'%Y%m%d')_${PLATFORM_PAIR}"
|
||||
package_dir="$base_dir/$name"
|
||||
package_file="$base_dir/$name.tar.gz"
|
||||
|
||||
# bin file
|
||||
mkdir -p $SUB_DIR
|
||||
docker run --rm --entrypoint cat ${{ env.DOCKER_IMG }} /artalk > "$SUB_DIR/artalk"
|
||||
chmod +x "$SUB_DIR/artalk"
|
||||
mkdir -p "$package_dir"
|
||||
docker run --rm --entrypoint cat "$IMAGE" /artalk > "$package_dir/artalk"
|
||||
chmod +x "$package_dir/artalk"
|
||||
|
||||
# doc file
|
||||
cp conf/artalk.example.yml "$SUB_DIR/artalk.yml"
|
||||
cp README.md LICENSE CHANGELOG.md "$SUB_DIR"
|
||||
cp conf/artalk.example.yml "$package_dir/artalk.yml"
|
||||
cp README.md LICENSE CHANGELOG.md "$package_dir"
|
||||
|
||||
# create package file
|
||||
tar -czf $PKG_FILE -C $BASE_DIR $NAME
|
||||
echo "app_pkg_file=$PKG_FILE" >> $GITHUB_OUTPUT
|
||||
tar -czf "$package_file" -C "$base_dir" "$name"
|
||||
echo "package_file=$package_file" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Move build artifacts
|
||||
- name: Move build artifacts
|
||||
- name: Collect build artifacts
|
||||
env:
|
||||
APP_PACKAGE: ${{ steps.app.outputs.package_file }}
|
||||
DOCKER_IMAGE: ${{ steps.docker.outputs.image_file }}
|
||||
run: |
|
||||
mkdir -p $DIST_DIR
|
||||
mv ${{ steps.app.outputs.app_pkg_file }} $DIST_DIR
|
||||
mv ${{ steps.docker.outputs.img_file }} $DIST_DIR
|
||||
mkdir -p "$DIST_DIR"
|
||||
mv "$APP_PACKAGE" "$DIST_DIR"
|
||||
mv "$DOCKER_IMAGE" "$DIST_DIR"
|
||||
|
||||
# Upload build artifacts
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: build-${{ env.PLATFORM_PAIR }}
|
||||
name: build-${{ matrix.platform_pair }}
|
||||
path: ${{ env.DIST_DIR }}/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
# Merge docker digests from different platforms
|
||||
# Reference: https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners
|
||||
docker_merge:
|
||||
runs-on: ubuntu-latest
|
||||
name: Publish Nightly Docker manifest
|
||||
if: ${{ github.event_name == 'schedule' || !inputs.dry_run }}
|
||||
needs: build
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: ${{ env.DIGEST_DIR }}
|
||||
pattern: digests-*
|
||||
pattern: nightly-digest-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: Docker meta
|
||||
- name: Generate Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
||||
with:
|
||||
images: ${{ env.DOCKER_IMG }}
|
||||
tags: type=raw,value=nightly
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Create manifest list and push
|
||||
working-directory: ${{ env.DIGEST_DIR }}
|
||||
- name: Create and push manifest
|
||||
env:
|
||||
METADATA: ${{ steps.meta.outputs.json }}
|
||||
run: |
|
||||
TAGS_ARG=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON")
|
||||
MINIFEST_SRCS=$(printf '${{ env.DOCKER_IMG }}@sha256:%s ' *)
|
||||
mapfile -t tags < <(jq -r '.tags[]' <<< "$METADATA")
|
||||
tag_args=()
|
||||
for tag in "${tags[@]}"; do
|
||||
tag_args+=(--tag "$tag")
|
||||
done
|
||||
|
||||
docker buildx imagetools create ${TAGS_ARG} ${MINIFEST_SRCS}
|
||||
shopt -s nullglob
|
||||
digest_files=("$DIGEST_DIR"/*)
|
||||
if [[ "${#digest_files[@]}" -ne 2 ]]; then
|
||||
echo "Expected 2 platform digests, found ${#digest_files[@]}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sources=()
|
||||
for digest_file in "${digest_files[@]}"; do
|
||||
digest="$(basename "$digest_file")"
|
||||
if [[ ! "$digest" =~ ^[0-9a-f]{64}$ ]]; then
|
||||
echo "Invalid image digest: $digest"
|
||||
exit 1
|
||||
fi
|
||||
sources+=("$DOCKER_IMG@sha256:$digest")
|
||||
done
|
||||
|
||||
docker buildx imagetools create "${tag_args[@]}" "${sources[@]}"
|
||||
|
||||
- name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ env.DOCKER_IMG }}:${{ steps.meta.outputs.version }}
|
||||
env:
|
||||
IMAGE: ${{ env.DOCKER_IMG }}:${{ steps.meta.outputs.version }}
|
||||
run: docker buildx imagetools inspect "$IMAGE"
|
||||
|
||||
# Release
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
name: Publish Nightly GitHub release
|
||||
if: ${{ github.event_name == 'schedule' || !inputs.dry_run }}
|
||||
needs: [build, docker_merge]
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download builds
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: ${{ env.DIST_DIR }}
|
||||
pattern: build-*
|
||||
merge-multiple: true
|
||||
|
||||
# Generate CHANGELOG
|
||||
- name: Generate Changelog
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: false
|
||||
|
||||
- name: Setup git-chglog
|
||||
run: |
|
||||
# install git-chglog
|
||||
curl -sL $(curl -s https://api.github.com/repos/git-chglog/git-chglog/releases/latest \
|
||||
| grep -oP '"https://.+linux_amd64.tar.gz"' | tr -d \") | tar -C /usr/local/bin -xz git-chglog
|
||||
GOBIN="$RUNNER_TEMP/bin" go install github.com/git-chglog/git-chglog/cmd/git-chglog@v0.15.4
|
||||
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
|
||||
go version -m "$RUNNER_TEMP/bin/git-chglog"
|
||||
|
||||
CHANGELOG=$(git-chglog --config .github/chglog/config.yml --next-tag nightly nightly)
|
||||
MD_FILE="/tmp/changelog.md"
|
||||
echo -e "$CHANGELOG" > $MD_FILE
|
||||
echo -e "\n> [!NOTE]\n" \
|
||||
"> The Nightly version, automatically built based on the latest code, " \
|
||||
"is not yet released and may be unstable. Please use it with caution. " \
|
||||
"Docker user can run \`docker pull artalk/artalk-go:nightly\` to get " \
|
||||
"the nightly build." >> $MD_FILE
|
||||
|
||||
# checksums.txt
|
||||
- name: Calculate checksums.txt
|
||||
- name: Generate changelog
|
||||
run: |
|
||||
cd $DIST_DIR
|
||||
sha256sum * > checksums.txt
|
||||
git-chglog --config .github/chglog/config.yml --next-tag nightly nightly > /tmp/changelog.md
|
||||
cat >> /tmp/changelog.md <<'EOF'
|
||||
|
||||
# print checksums.txt
|
||||
> [!NOTE]
|
||||
> The Nightly version is automatically built from the latest code and may be unstable.
|
||||
> Docker users can run `docker pull artalk/artalk-go:nightly` to get this build.
|
||||
EOF
|
||||
|
||||
- name: Calculate checksums
|
||||
run: |
|
||||
cd "$DIST_DIR"
|
||||
sha256sum -- * > checksums.txt
|
||||
cat checksums.txt
|
||||
|
||||
FILES=$(find ${DIST_DIR} -type f -exec readlink -f {} \;)
|
||||
echo -e "RELEASE_FILES<<EOF" >> $GITHUB_ENV
|
||||
echo -e "$FILES" >> $GITHUB_ENV
|
||||
echo -e "EOF" >> $GITHUB_ENV
|
||||
|
||||
- name: Create tag and push
|
||||
- name: Move Nightly tag
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
git config user.name github-actions[bot]
|
||||
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
|
||||
if gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/nightly" > /dev/null 2>&1; then
|
||||
gh api --method PATCH "repos/$GITHUB_REPOSITORY/git/refs/tags/nightly" \
|
||||
-f sha="$GITHUB_SHA" -F force=true > /dev/null
|
||||
else
|
||||
gh api --method POST "repos/$GITHUB_REPOSITORY/git/refs" \
|
||||
-f ref=refs/tags/nightly -f sha="$GITHUB_SHA" > /dev/null
|
||||
fi
|
||||
|
||||
tag_sha="$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/nightly" --jq '.object.sha')"
|
||||
if [[ "$tag_sha" != "$GITHUB_SHA" ]]; then
|
||||
echo "Nightly tag points to $tag_sha, expected $GITHUB_SHA."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Publish release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh release delete nightly --yes || true
|
||||
git push origin :refs/tags/nightly || true
|
||||
|
||||
git tag -f nightly
|
||||
git push -f origin nightly
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
prerelease: true
|
||||
tag_name: nightly
|
||||
name: 🧪 Nightly Version
|
||||
body_path: /tmp/changelog.md
|
||||
files: ${{ env.RELEASE_FILES }}
|
||||
draft: false
|
||||
mapfile -d '' release_files < <(find "$DIST_DIR" -type f -print0)
|
||||
gh release create nightly "${release_files[@]}" \
|
||||
--title "🧪 Nightly Version" \
|
||||
--notes-file /tmp/changelog.md \
|
||||
--prerelease \
|
||||
--target "$GITHUB_SHA"
|
||||
@@ -9,53 +9,257 @@ on:
|
||||
issue_number:
|
||||
required: true
|
||||
type: number
|
||||
version:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
release_commit:
|
||||
description: "Immutable commit to tag when the release tag does not exist"
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
outputs:
|
||||
version:
|
||||
description: "Version"
|
||||
value: ${{ jobs.tagging.outputs.version }}
|
||||
ref:
|
||||
description: "Git ref to build"
|
||||
value: ${{ jobs.tagging.outputs.ref }}
|
||||
commit:
|
||||
description: "Release source commit"
|
||||
value: ${{ jobs.tagging.outputs.commit }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
tagging:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get release version
|
||||
id: version
|
||||
env:
|
||||
REF: ${{ github.head_ref || github.ref }}
|
||||
REQUESTED_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
# `github.head_ref` only exists for pull_request events
|
||||
REF="${{ github.head_ref || github.ref }}"
|
||||
VERSION="${REF#refs/tags/}"
|
||||
VERSION="${VERSION#refs/heads/}"
|
||||
VERSION="${VERSION#release/}"
|
||||
|
||||
echo "Version: $VERSION"
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
if [[ -n "$REQUESTED_VERSION" ]]; then
|
||||
VERSION="$REQUESTED_VERSION"
|
||||
else
|
||||
VERSION="${REF#refs/tags/}"
|
||||
VERSION="${VERSION#refs/heads/}"
|
||||
VERSION="${VERSION#release/}"
|
||||
fi
|
||||
|
||||
if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Invalid version format: $VERSION, please check git ref or tag."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Version: $VERSION"
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Resolve release target
|
||||
id: target
|
||||
env:
|
||||
DEFAULT_COMMIT: ${{ github.sha }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
REQUESTED_COMMIT: ${{ inputs.release_commit }}
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
resolve_tag_commit() {
|
||||
local object_type="$1"
|
||||
local object_sha="$2"
|
||||
local depth=0
|
||||
|
||||
while [[ "$object_type" == "tag" ]]; do
|
||||
depth=$((depth + 1))
|
||||
if (( depth > 8 )); then
|
||||
echo "Tag $VERSION has too many nested tag objects." >&2
|
||||
return 1
|
||||
fi
|
||||
IFS=$'\t' read -r object_type object_sha < <(
|
||||
gh api "repos/$GITHUB_REPOSITORY/git/tags/$object_sha" \
|
||||
--jq '.object | [.type, .sha] | @tsv'
|
||||
)
|
||||
done
|
||||
|
||||
if [[ "$object_type" != "commit" ]]; then
|
||||
echo "Tag $VERSION resolves to unsupported object type $object_type." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "$object_sha"
|
||||
}
|
||||
|
||||
requested_commit="$(tr '[:upper:]' '[:lower:]' <<< "$REQUESTED_COMMIT")"
|
||||
if [[ -n "$requested_commit" && ! "$requested_commit" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
echo "release_commit must be a full 40-character commit SHA."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tag_ref="$(
|
||||
gh api "repos/$GITHUB_REPOSITORY/git/matching-refs/tags/$VERSION" \
|
||||
--jq ".[] | select(.ref == \"refs/tags/$VERSION\") | [.object.type, .object.sha] | @tsv"
|
||||
)"
|
||||
|
||||
tag_exists=false
|
||||
if [[ -n "$tag_ref" ]]; then
|
||||
IFS=$'\t' read -r tag_type tag_sha <<< "$tag_ref"
|
||||
release_commit="$(resolve_tag_commit "$tag_type" "$tag_sha")"
|
||||
tag_exists=true
|
||||
if [[ -n "$requested_commit" && "$release_commit" != "$requested_commit" ]]; then
|
||||
echo "Tag $VERSION points to $release_commit, not requested commit $requested_commit."
|
||||
exit 1
|
||||
fi
|
||||
elif [[ -n "$requested_commit" ]]; then
|
||||
release_commit="$requested_commit"
|
||||
elif [[ "$DRY_RUN" == "true" ]]; then
|
||||
release_commit="$DEFAULT_COMMIT"
|
||||
else
|
||||
echo "Tag $VERSION does not exist and release_commit was not provided."
|
||||
echo "Use the reviewed release pull request merge commit for a production retry."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verified_commit="$(
|
||||
gh api "repos/$GITHUB_REPOSITORY/git/commits/$release_commit" --jq '.sha'
|
||||
)"
|
||||
if [[ "$verified_commit" != "$release_commit" ]]; then
|
||||
echo "Unable to verify release commit $release_commit."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "commit=$release_commit" >> "$GITHUB_OUTPUT"
|
||||
echo "tag_exists=$tag_exists" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ steps.target.outputs.commit }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Validate release source
|
||||
env:
|
||||
RELEASE_COMMIT: ${{ steps.target.outputs.commit }}
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
checkout_commit="$(git rev-parse HEAD)"
|
||||
if [[ "$checkout_commit" != "$RELEASE_COMMIT" ]]; then
|
||||
echo "Checked out $checkout_commit, expected $RELEASE_COMMIT."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git fetch --no-tags origin \
|
||||
"+refs/heads/master:refs/remotes/origin/master"
|
||||
if ! git merge-base --is-ancestor "$RELEASE_COMMIT" origin/master; then
|
||||
echo "Release commit $RELEASE_COMMIT is not reachable from master."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
package_version="${VERSION#v}"
|
||||
app_version="$(
|
||||
sed -n 's/^var Version = "\(v[0-9][0-9.]*\)"$/\1/p' internal/config/version.go
|
||||
)"
|
||||
ui_version="$(jq -r '.version' ui/artalk/package.json)"
|
||||
docs_version="$(jq -r '.latest' docs/docs/code/ArtalkVersion.json)"
|
||||
if [[ "$app_version" != "$VERSION" ||
|
||||
"$ui_version" != "$package_version" ||
|
||||
"$docs_version" != "$package_version" ]]; then
|
||||
echo "Release commit versions do not match $VERSION: app=$app_version ui=$ui_version docs=$docs_version."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Create Tag
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_COMMIT: ${{ steps.target.outputs.commit }}
|
||||
TAG_EXISTS: ${{ steps.target.outputs.tag_exists }}
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
if [ ! $(git tag -l "${VERSION}") ]; then
|
||||
git config user.name github-actions[bot]
|
||||
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
|
||||
git tag -a $VERSION -m "Release $VERSION"
|
||||
git push origin $VERSION
|
||||
else
|
||||
echo "Skip creating tag $VERSION, already exists."
|
||||
resolve_tag_commit() {
|
||||
local object_type="$1"
|
||||
local object_sha="$2"
|
||||
local depth=0
|
||||
|
||||
while [[ "$object_type" == "tag" ]]; do
|
||||
depth=$((depth + 1))
|
||||
if (( depth > 8 )); then
|
||||
echo "Tag $VERSION has too many nested tag objects." >&2
|
||||
return 1
|
||||
fi
|
||||
IFS=$'\t' read -r object_type object_sha < <(
|
||||
gh api "repos/$GITHUB_REPOSITORY/git/tags/$object_sha" \
|
||||
--jq '.object | [.type, .sha] | @tsv'
|
||||
)
|
||||
done
|
||||
|
||||
if [[ "$object_type" != "commit" ]]; then
|
||||
echo "Tag $VERSION resolves to unsupported object type $object_type." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "$object_sha"
|
||||
}
|
||||
|
||||
get_tag_ref() {
|
||||
gh api "repos/$GITHUB_REPOSITORY/git/matching-refs/tags/$VERSION" \
|
||||
--jq ".[] | select(.ref == \"refs/tags/$VERSION\") | [.object.type, .object.sha] | @tsv"
|
||||
}
|
||||
|
||||
if [[ "$TAG_EXISTS" != "true" ]]; then
|
||||
tag_object_sha="$(
|
||||
gh api --method POST "repos/$GITHUB_REPOSITORY/git/tags" \
|
||||
-f tag="$VERSION" \
|
||||
-f message="Release $VERSION" \
|
||||
-f object="$RELEASE_COMMIT" \
|
||||
-f type=commit \
|
||||
--jq '.sha'
|
||||
)"
|
||||
|
||||
if ! create_error="$(
|
||||
gh api --method POST "repos/$GITHUB_REPOSITORY/git/refs" \
|
||||
-f ref="refs/tags/$VERSION" \
|
||||
-f sha="$tag_object_sha" 2>&1
|
||||
)"; then
|
||||
tag_ref="$(get_tag_ref)"
|
||||
if [[ -z "$tag_ref" ]]; then
|
||||
printf '%s\n' "$create_error" >&2
|
||||
echo "GitHub rejected tag $VERSION. Check the active tag rulesets; tag targets must not require linear history." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Tag $VERSION was created concurrently; verifying it."
|
||||
else
|
||||
echo "Created annotated tag $VERSION at $RELEASE_COMMIT."
|
||||
fi
|
||||
fi
|
||||
|
||||
tag_ref="$(get_tag_ref)"
|
||||
if [[ -z "$tag_ref" ]]; then
|
||||
echo "Tag $VERSION is missing after creation."
|
||||
exit 1
|
||||
fi
|
||||
IFS=$'\t' read -r tag_type tag_sha <<< "$tag_ref"
|
||||
tag_commit="$(resolve_tag_commit "$tag_type" "$tag_sha")"
|
||||
if [[ "$tag_commit" != "$RELEASE_COMMIT" ]]; then
|
||||
echo "Tag $VERSION points to $tag_commit, expected $RELEASE_COMMIT."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Verified tag $VERSION at $RELEASE_COMMIT."
|
||||
|
||||
- name: Comment tag info to PR
|
||||
uses: ./.github/actions/comment
|
||||
if: ${{ inputs.issue_number != 0 }}
|
||||
if: ${{ !inputs.dry_run && inputs.issue_number != 0 }}
|
||||
with:
|
||||
issue_number: ${{ inputs.issue_number }}
|
||||
content: |-
|
||||
🔖 Tagged version [${{ env.VERSION }}](https://github.com/ArtalkJS/Artalk/tree/${{ env.VERSION }}) and updated the [CHANGELOG.md](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md).
|
||||
🔖 Tagged version [${{ steps.version.outputs.version }}](https://github.com/ArtalkJS/Artalk/tree/${{ steps.version.outputs.version }}) and updated the [CHANGELOG.md](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md).
|
||||
outputs:
|
||||
version: ${{ env.VERSION }}
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
ref: ${{ inputs.dry_run && steps.target.outputs.commit || steps.version.outputs.version }}
|
||||
commit: ${{ steps.target.outputs.commit }}
|
||||
@@ -6,31 +6,38 @@ on:
|
||||
version:
|
||||
required: true
|
||||
type: string
|
||||
ref:
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
required: true
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
build_publish:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.version }}
|
||||
ref: ${{ inputs.ref }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Use pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: 9.10.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: "pnpm"
|
||||
node-version: 24.x
|
||||
package-manager-cache: false
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
@@ -38,15 +45,37 @@ jobs:
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Publish
|
||||
run: pnpm publish -F artalk --no-git-checks ${{ inputs.dry_run && '--dry-run' || '' }}
|
||||
- name: Pack npm package
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
package_version="v$(node --print "require('./ui/artalk/package.json').version")"
|
||||
if [[ "$package_version" != "$VERSION" ]]; then
|
||||
echo "Package version $package_version does not match release version $VERSION."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pnpm --filter artalk pack --pack-destination "$RUNNER_TEMP"
|
||||
package_file="$RUNNER_TEMP/artalk-${package_version#v}.tgz"
|
||||
if [[ ! -f "$package_file" ]]; then
|
||||
echo "Expected package tarball was not created: $package_file"
|
||||
exit 1
|
||||
fi
|
||||
echo "PACKAGE_FILE=$package_file" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Validate package
|
||||
if: ${{ inputs.dry_run }}
|
||||
run: npm publish "$PACKAGE_FILE" --dry-run --access public
|
||||
|
||||
- name: Publish
|
||||
if: ${{ !inputs.dry_run }}
|
||||
# npm validates build.yml, the calling workflow, for trusted publishing.
|
||||
run: npm publish "$PACKAGE_FILE" --access public
|
||||
|
||||
# GitHub Packages
|
||||
#
|
||||
# Setup .npmrc file to publish to GitHub Packages
|
||||
# - uses: actions/setup-node@v4
|
||||
# - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
# with:
|
||||
# registry-url: 'https://npm.pkg.github.com'
|
||||
# # Publish to GitHub Packages
|
||||
|
||||
+61
-19
@@ -1,5 +1,5 @@
|
||||
name: Build and Publish
|
||||
run-name: Build and Publish (${{ github.head_ref || github.ref }}) ${{ inputs.dry_run && '(🧪 Dry-Run)' || '' }}
|
||||
run-name: Build and Publish (${{ inputs.version || github.head_ref || github.ref }}) ${{ inputs.dry_run && '(🧪 Dry-Run)' || '' }}
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -12,11 +12,34 @@ on:
|
||||
description: "Dry run"
|
||||
type: boolean
|
||||
default: true
|
||||
version:
|
||||
description: "Release version (for example, v2.10.0)"
|
||||
type: string
|
||||
required: true
|
||||
release_commit:
|
||||
description: "Full release commit SHA (required when a production retry has no tag)"
|
||||
type: string
|
||||
required: false
|
||||
build_items:
|
||||
description: "Build items"
|
||||
type: string
|
||||
type: choice
|
||||
options:
|
||||
- "ui,app,docker"
|
||||
- "ui,app"
|
||||
- "ui,docker"
|
||||
- "app,docker"
|
||||
- ui
|
||||
- app
|
||||
- docker
|
||||
default: "ui,app,docker"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: release-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
# Tag the release version code before building
|
||||
# Getting the version code from the branch name or git ref.
|
||||
@@ -26,57 +49,76 @@ jobs:
|
||||
# (which the branch name start with `relase/`)
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event.pull_request.merged == true && startsWith(github.head_ref, 'release/'))
|
||||
(github.event.pull_request.merged == true &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository &&
|
||||
startsWith(github.head_ref, 'release/'))
|
||||
uses: ./.github/workflows/build-tagging.yml
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
with:
|
||||
issue_number: ${{ github.event.pull_request.number || 0 }}
|
||||
dry_run: ${{ inputs.dry_run || false }}
|
||||
secrets: inherit
|
||||
version: ${{ inputs.version || '' }}
|
||||
release_commit: ${{ github.event.pull_request.merge_commit_sha || inputs.release_commit || '' }}
|
||||
|
||||
# Build the UI
|
||||
ui:
|
||||
if: inputs.build_items == '' || contains(inputs.build_items, 'ui')
|
||||
if: inputs.build_items == '' || contains(format(',{0},', inputs.build_items), ',ui,')
|
||||
needs: tagging
|
||||
uses: ./.github/workflows/build-ui.yml
|
||||
with:
|
||||
version: ${{ needs.tagging.outputs.version }}
|
||||
ref: ${{ needs.tagging.outputs.ref }}
|
||||
dry_run: ${{ inputs.dry_run || false }}
|
||||
secrets: inherit
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
# Build the Docker image
|
||||
docker:
|
||||
if: inputs.build_items == '' || contains(inputs.build_items, 'docker')
|
||||
if: inputs.build_items == '' || contains(format(',{0},', inputs.build_items), ',docker,')
|
||||
needs: tagging
|
||||
uses: ./.github/workflows/build-docker.yml
|
||||
# Parallel build the multi-arch docker image
|
||||
strategy:
|
||||
matrix:
|
||||
# since docker push actions will override the previous images,
|
||||
# so need to build `linux/amd64` and push first,
|
||||
# then build `linux/amd64` twice with other archs (build very slow so keep it last).
|
||||
target: ["linux/amd64", "linux/amd64,linux/arm64"]
|
||||
with:
|
||||
version: ${{ needs.tagging.outputs.version }}
|
||||
ref: ${{ needs.tagging.outputs.ref }}
|
||||
commit: ${{ needs.tagging.outputs.commit }}
|
||||
dry_run: ${{ inputs.dry_run || false }}
|
||||
target: ${{ matrix.target }}
|
||||
secrets: inherit
|
||||
permissions:
|
||||
contents: read
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
# Build the binary app
|
||||
app:
|
||||
if: inputs.build_items == '' || contains(inputs.build_items, 'app')
|
||||
if: inputs.build_items == '' || contains(format(',{0},', inputs.build_items), ',app,')
|
||||
needs: tagging
|
||||
uses: ./.github/workflows/build-app.yml
|
||||
with:
|
||||
version: ${{ needs.tagging.outputs.version }}
|
||||
ref: ${{ needs.tagging.outputs.ref }}
|
||||
dry_run: ${{ inputs.dry_run || false }}
|
||||
secrets: inherit
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
# All builds are complete
|
||||
done:
|
||||
needs: [tagging, ui, docker, app]
|
||||
if: |
|
||||
always() &&
|
||||
needs.tagging.result == 'success' &&
|
||||
!contains(needs.*.result, 'failure') &&
|
||||
!contains(needs.*.result, 'cancelled')
|
||||
uses: ./.github/workflows/build-done.yml
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
with:
|
||||
version: ${{ needs.tagging.outputs.version }}
|
||||
ref: ${{ needs.tagging.outputs.ref }}
|
||||
issue_number: ${{ github.event.pull_request.number || 0 }}
|
||||
dry_run: ${{ inputs.dry_run || false }}
|
||||
secrets: inherit
|
||||
@@ -22,6 +22,13 @@ on:
|
||||
schedule:
|
||||
- cron: "40 17 * * 4"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: codeql-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
@@ -42,11 +49,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -59,7 +68,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
uses: github/codeql-action/autobuild@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
@@ -72,6 +81,6 @@ jobs:
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
@@ -5,33 +5,42 @@ on:
|
||||
branches: ["master"]
|
||||
paths: ["docs/**"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: deploy-docs-cn
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Use pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: 9.10.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build Docs
|
||||
run: pnpm build:docs
|
||||
|
||||
- name: Deploy
|
||||
uses: TencentCloud/cos-action@v1
|
||||
uses: TencentCloud/cos-action@5ee3807d5ff6fddadbbb1d3e658c0690863de8ec # v1
|
||||
with:
|
||||
secret_id: ${{ secrets.TENCENT_CLOUD_SECRET_ID }}
|
||||
secret_key: ${{ secrets.TENCENT_CLOUD_SECRET_KEY }}
|
||||
|
||||
+178
-26
@@ -21,67 +21,219 @@ on:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: prepare-release
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: master
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
registry-url: https://registry.npmjs.org/
|
||||
|
||||
- name: Setup semver
|
||||
run: npm install -g semver
|
||||
persist-credentials: false
|
||||
|
||||
- name: Handle Version Number
|
||||
env:
|
||||
CUSTOM_VERSION: ${{ inputs.custom_version }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
SEMVER_INCREMENT: ${{ inputs.semver }}
|
||||
run: |
|
||||
PREV_VERSION="$(git describe --tags --abbrev=0 --match 'v*')"
|
||||
|
||||
if [ -n "${{ inputs.custom_version }}" ]; then
|
||||
NEXT_VERSION="${{ inputs.custom_version }}"
|
||||
else
|
||||
NEXT_VERSION="v$(semver --increment ${{ inputs.semver }} ${PREV_VERSION})"
|
||||
if [[ ! "$PREV_VERSION" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||
echo "Invalid previous version: $PREV_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "PREV_VERSION=${PREV_VERSION}" >> $GITHUB_ENV
|
||||
echo "VERSION=${NEXT_VERSION}" >> $GITHUB_ENV
|
||||
if ! git merge-base --is-ancestor "$PREV_VERSION" HEAD; then
|
||||
echo "Latest release tag $PREV_VERSION is not an ancestor of master."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CURRENT_APP_VERSION="$(
|
||||
sed -n 's/^var Version = "\(v[0-9][0-9.]*\)"$/\1/p' internal/config/version.go
|
||||
)"
|
||||
CURRENT_UI_VERSION="$(jq -r '.version' ui/artalk/package.json)"
|
||||
CURRENT_DOCS_VERSION="$(jq -r '.latest' docs/docs/code/ArtalkVersion.json)"
|
||||
CURRENT_PACKAGE_VERSION="${CURRENT_APP_VERSION#v}"
|
||||
|
||||
if [[ -z "$CURRENT_APP_VERSION" ||
|
||||
"$CURRENT_UI_VERSION" != "$CURRENT_PACKAGE_VERSION" ||
|
||||
"$CURRENT_DOCS_VERSION" != "$CURRENT_PACKAGE_VERSION" ]]; then
|
||||
echo "Release versions are inconsistent: app=$CURRENT_APP_VERSION ui=$CURRENT_UI_VERSION docs=$CURRENT_DOCS_VERSION."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$CURRENT_APP_VERSION" != "$PREV_VERSION" ]]; then
|
||||
echo "Source version $CURRENT_APP_VERSION does not match the latest release tag $PREV_VERSION."
|
||||
echo "If the release PR was already merged, recover it with Build and Publish instead of preparing the release again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "$CUSTOM_VERSION" ]]; then
|
||||
NEXT_VERSION="$CUSTOM_VERSION"
|
||||
else
|
||||
major="${BASH_REMATCH[1]}"
|
||||
minor="${BASH_REMATCH[2]}"
|
||||
patch="${BASH_REMATCH[3]}"
|
||||
case "$SEMVER_INCREMENT" in
|
||||
major) NEXT_VERSION="v$((major + 1)).0.0" ;;
|
||||
minor) NEXT_VERSION="v${major}.$((minor + 1)).0" ;;
|
||||
patch) NEXT_VERSION="v${major}.${minor}.$((patch + 1))" ;;
|
||||
*) echo "Invalid version increment: $SEMVER_INCREMENT"; exit 1 ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [[ ! "$NEXT_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Invalid version: $NEXT_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version_is_greater() {
|
||||
local next_version="${1#v}"
|
||||
local previous_version="${2#v}"
|
||||
local -a next_parts previous_parts
|
||||
local index next_part previous_part
|
||||
|
||||
IFS='.' read -r -a next_parts <<< "$next_version"
|
||||
IFS='.' read -r -a previous_parts <<< "$previous_version"
|
||||
for index in 0 1 2; do
|
||||
next_part=$((10#${next_parts[$index]}))
|
||||
previous_part=$((10#${previous_parts[$index]}))
|
||||
if (( next_part > previous_part )); then
|
||||
return 0
|
||||
fi
|
||||
if (( next_part < previous_part )); then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
if ! version_is_greater "$NEXT_VERSION" "$PREV_VERSION"; then
|
||||
echo "Release version $NEXT_VERSION must be greater than $PREV_VERSION."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if git show-ref --verify --quiet "refs/tags/$NEXT_VERSION"; then
|
||||
echo "Release tag $NEXT_VERSION already exists."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
release_branch="release/$NEXT_VERSION"
|
||||
existing_pr="$(
|
||||
gh pr list \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--head "$release_branch" \
|
||||
--base master \
|
||||
--state open \
|
||||
--json number,url \
|
||||
--jq 'if length == 0 then "" else "#\(.[0].number) \(.[0].url)" end'
|
||||
)"
|
||||
if [[ -n "$existing_pr" ]]; then
|
||||
echo "Release pull request $existing_pr already exists for $NEXT_VERSION."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "PREV_VERSION=${PREV_VERSION}" >> "$GITHUB_ENV"
|
||||
echo "VERSION=${NEXT_VERSION}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Print Next Version
|
||||
run: echo "${VERSION}"
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: false
|
||||
|
||||
- name: Setup git-chglog
|
||||
run: |
|
||||
curl -sL $(curl -s https://api.github.com/repos/git-chglog/git-chglog/releases/latest \
|
||||
| grep -oP '"https://.+linux_amd64.tar.gz"' | tr -d \") | tar -C /usr/local/bin -xz git-chglog
|
||||
git-chglog --version
|
||||
GOBIN="$RUNNER_TEMP/bin" go install github.com/git-chglog/git-chglog/cmd/git-chglog@v0.15.4
|
||||
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
|
||||
go version -m "$RUNNER_TEMP/bin/git-chglog"
|
||||
|
||||
- name: Version Changelog
|
||||
run: |
|
||||
# prepend changelog
|
||||
changelog=$(git-chglog --config .github/chglog/config.yml --next-tag "${VERSION}" "${VERSION}")
|
||||
echo -e "${changelog}\n" | cat - CHANGELOG.md > temp && mv temp CHANGELOG.md
|
||||
changelog="$(git-chglog --config .github/chglog/config.yml --next-tag "$VERSION" "$VERSION")"
|
||||
temp_file="$(mktemp CHANGELOG.md.XXXXXX)"
|
||||
printf '%s\n\n' "$changelog" | cat - CHANGELOG.md > "$temp_file"
|
||||
mv "$temp_file" CHANGELOG.md
|
||||
|
||||
# modify the version for `internal/config/version.go`
|
||||
perl -i -pe 's/const Version = ".*?"/const Version = "'${VERSION}'"/g' internal/config/version.go
|
||||
perl -i -pe 's/var Version = ".*?"/var Version = "$ENV{VERSION}"/g' internal/config/version.go
|
||||
|
||||
# modify the version for `package.json`
|
||||
perl -i -pe 's/"version": ".*?"/"version": "'${VERSION#v}'"/g' ui/artalk/package.json
|
||||
PACKAGE_VERSION="${VERSION#v}"
|
||||
PACKAGE_VERSION="$PACKAGE_VERSION" perl -i -pe \
|
||||
's/"version": ".*?"/"version": "$ENV{PACKAGE_VERSION}"/g' ui/artalk/package.json
|
||||
|
||||
# modify the version in docs
|
||||
perl -pi -e 's#"latest"(\W+)?:(\W+)?".*?"#"latest": "'${VERSION#v}'"#g' docs/docs/code/ArtalkVersion.json
|
||||
PACKAGE_VERSION="$PACKAGE_VERSION" perl -pi -e \
|
||||
's#"latest"(\W+)?:(\W+)?".*?"#"latest": "$ENV{PACKAGE_VERSION}"#g' docs/docs/code/ArtalkVersion.json
|
||||
|
||||
- name: Validate Release Changes
|
||||
run: |
|
||||
PACKAGE_VERSION="${VERSION#v}"
|
||||
APP_VERSION="$(
|
||||
sed -n 's/^var Version = "\(v[0-9][0-9.]*\)"$/\1/p' internal/config/version.go
|
||||
)"
|
||||
UI_VERSION="$(jq -r '.version' ui/artalk/package.json)"
|
||||
DOCS_VERSION="$(jq -r '.latest' docs/docs/code/ArtalkVersion.json)"
|
||||
|
||||
if [[ "$APP_VERSION" != "$VERSION" ||
|
||||
"$UI_VERSION" != "$PACKAGE_VERSION" ||
|
||||
"$DOCS_VERSION" != "$PACKAGE_VERSION" ]]; then
|
||||
echo "Generated release versions are inconsistent: app=$APP_VERSION ui=$UI_VERSION docs=$DOCS_VERSION."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
changelog_entries="$(grep -cF "## [$VERSION]" CHANGELOG.md || true)"
|
||||
if [[ "$changelog_entries" != "1" ]]; then
|
||||
echo "Expected one CHANGELOG entry for $VERSION, found $changelog_entries."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
first_changelog_version="$(
|
||||
sed -n 's/^## \[\(v[0-9][^]]*\)\].*/\1/p' CHANGELOG.md | head -n 1
|
||||
)"
|
||||
if [[ "$first_changelog_version" != "$VERSION" ]]; then
|
||||
echo "The first CHANGELOG release is $first_changelog_version, expected $VERSION."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
expected_changes="$(
|
||||
printf '%s\n' \
|
||||
CHANGELOG.md \
|
||||
docs/docs/code/ArtalkVersion.json \
|
||||
internal/config/version.go \
|
||||
ui/artalk/package.json |
|
||||
sort
|
||||
)"
|
||||
actual_changes="$(git diff --name-only | sort)"
|
||||
if [[ "$actual_changes" != "$expected_changes" ]]; then
|
||||
echo "Unexpected release file set:"
|
||||
git diff --name-only
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git diff --check
|
||||
|
||||
- name: Print Git Diff
|
||||
run: git diff
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
if: ${{ !inputs.dry_run }}
|
||||
with:
|
||||
branch: "release/${{ env.VERSION }}"
|
||||
|
||||
@@ -10,11 +10,23 @@ on:
|
||||
required: true
|
||||
default: ""
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Validate event type
|
||||
env:
|
||||
EVENT_TYPE: ${{ inputs.event_type }}
|
||||
run: |
|
||||
if [[ ! "$EVENT_TYPE" =~ ^[A-Za-z0-9_.-]{1,100}$ ]]; then
|
||||
echo "Invalid repository dispatch event type."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Repository Dispatch
|
||||
uses: peter-evans/repository-dispatch@v3
|
||||
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1
|
||||
with:
|
||||
event-type: ${{ inputs.event_type }}
|
||||
@@ -2,30 +2,69 @@ name: Test App
|
||||
|
||||
on:
|
||||
push:
|
||||
paths: ["cmd/**", "internal/**", "server/**", "test/**", "main.go", "go.mod", "go.sum"]
|
||||
paths:
|
||||
- ".github/workflows/test-app.yml"
|
||||
- "cmd/**"
|
||||
- "internal/**"
|
||||
- "server/**"
|
||||
- "test/**"
|
||||
- "main.go"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
branches-ignore: ["release/*", "nightly"]
|
||||
tags-ignore: ["v*"]
|
||||
pull_request:
|
||||
paths: ["cmd/**", "internal/**", "server/**", "test/**", "main.go", "go.mod", "go.sum"]
|
||||
paths:
|
||||
- ".github/workflows/test-app.yml"
|
||||
- "cmd/**"
|
||||
- "internal/**"
|
||||
- "server/**"
|
||||
- "test/**"
|
||||
- "main.go"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
branches-ignore: ["release/*", "nightly"]
|
||||
|
||||
env:
|
||||
REPORT_DIR: "/tmp/test-reports"
|
||||
CGO_ENABLED: 0
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: test-app-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test_app:
|
||||
name: Test Go on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: Test Go (${{ matrix.name }})
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
include:
|
||||
- name: Linux AMD64
|
||||
runner: ubuntu-24.04
|
||||
artifact: linux-amd64
|
||||
coverage: true
|
||||
- name: Linux ARM64
|
||||
runner: ubuntu-24.04-arm
|
||||
artifact: linux-arm64
|
||||
coverage: false
|
||||
- name: macOS
|
||||
runner: macos-latest
|
||||
artifact: macos
|
||||
coverage: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: false # https://github.com/actions/setup-go/issues/483
|
||||
@@ -37,35 +76,43 @@ jobs:
|
||||
echo "GOMODCACHE=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache go modules
|
||||
uses: actions/cache@v4
|
||||
# v6.1.0; this test-only cache never feeds a publishing workflow.
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # zizmor: ignore[cache-poisoning]
|
||||
with:
|
||||
path: |
|
||||
${{ steps.go-vars.outputs.GOCACHE }}
|
||||
${{ steps.go-vars.outputs.GOMODCACHE }}
|
||||
key: go-test-${{ runner.os }}-${{ hashFiles('**/go.mod') }}
|
||||
key: go-test-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('go.sum') }}
|
||||
restore-keys: |
|
||||
go-test-${{ runner.os }}
|
||||
go-test-${{ runner.os }}-${{ runner.arch }}-
|
||||
|
||||
- name: Install tools
|
||||
run: go install gotest.tools/gotestsum@latest
|
||||
run: go install gotest.tools/gotestsum@v1.13.0
|
||||
|
||||
- name: Download Go modules
|
||||
run: go mod download
|
||||
|
||||
- name: Run vet
|
||||
run: go vet ./...
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
mkdir -p $REPORT_DIR
|
||||
gotestsum --junitfile $REPORT_DIR/unit-tests.xml --jsonfile $REPORT_DIR/unit-tests.json -- -coverprofile=$REPORT_DIR/coverage.out ./...
|
||||
mkdir -p "$REPORT_DIR"
|
||||
gotestsum \
|
||||
--junitfile "$REPORT_DIR/unit-tests.xml" \
|
||||
--jsonfile "$REPORT_DIR/unit-tests.json" \
|
||||
-- -coverprofile="$REPORT_DIR/coverage.out" ./...
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: Go-Test-Results-${{ runner.os }}
|
||||
name: Go-Test-Results-${{ matrix.artifact }}
|
||||
path: ${{ env.REPORT_DIR }}
|
||||
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
- if: ${{ matrix.coverage && !cancelled() }}
|
||||
name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
flags: go
|
||||
file: ${{ env.REPORT_DIR }}/coverage.out
|
||||
|
||||
@@ -9,27 +9,36 @@ on:
|
||||
paths: ["docs/**"]
|
||||
branches-ignore: ["release/*", "nightly"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: test-docs-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test_docs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Use pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: 9.10.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: pnpm
|
||||
node-version: 22.x
|
||||
cache: pnpm # zizmor: ignore[cache-poisoning] test-only cache
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build Docs
|
||||
run: pnpm build:docs
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
name: Test End-to-End
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- ".github/workflows/test-e2e.yml"
|
||||
- "cmd/**"
|
||||
- "conf/**"
|
||||
- "internal/**"
|
||||
- "server/**"
|
||||
- "ui/**"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- "package.json"
|
||||
- "pnpm-lock.yaml"
|
||||
branches-ignore: ["release/*", "nightly"]
|
||||
tags-ignore: ["v*"]
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/test-e2e.yml"
|
||||
- "cmd/**"
|
||||
- "conf/**"
|
||||
- "internal/**"
|
||||
- "server/**"
|
||||
- "ui/**"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- "package.json"
|
||||
- "pnpm-lock.yaml"
|
||||
branches-ignore: ["release/*", "nightly"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: test-e2e-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
jobs:
|
||||
test_e2e:
|
||||
name: Test browser UAT flow
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true # zizmor: ignore[cache-poisoning] test-only cache
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: 10.33.2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 22.x
|
||||
cache: pnpm # zizmor: ignore[cache-poisoning] test-only cache
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Install Chromium
|
||||
run: pnpm exec playwright install --with-deps chromium
|
||||
|
||||
- name: Run end-to-end tests
|
||||
run: pnpm -F artalk test:e2e
|
||||
|
||||
- name: Upload Playwright report
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: Playwright-Report
|
||||
path: ui/artalk/playwright-report
|
||||
if-no-files-found: ignore
|
||||
retention-days: 7
|
||||
@@ -2,22 +2,38 @@ name: Test Frontend
|
||||
|
||||
on:
|
||||
push:
|
||||
paths: ["ui/**"]
|
||||
paths: [".github/workflows/test-frontend.yml", "ui/**", "package.json", "pnpm-lock.yaml"]
|
||||
branches-ignore: ["release/*", "nightly"]
|
||||
tags-ignore: ["v*"]
|
||||
pull_request:
|
||||
paths: ["ui/**"]
|
||||
paths: [".github/workflows/test-frontend.yml", "ui/**", "package.json", "pnpm-lock.yaml"]
|
||||
branches-ignore: ["release/*", "nightly"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: test-frontend-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test_ui:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node: [18, 20]
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
include:
|
||||
- os: ubuntu-24.04
|
||||
node: 22
|
||||
quality: true
|
||||
- os: ubuntu-24.04
|
||||
node: 24
|
||||
quality: false
|
||||
- os: windows-latest
|
||||
node: 22
|
||||
quality: false
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- if: matrix.os == 'windows-latest'
|
||||
@@ -27,17 +43,19 @@ jobs:
|
||||
git config --global core.eol lf
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
version: 9.10.0
|
||||
persist-credentials: false
|
||||
|
||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: 10.33.2
|
||||
|
||||
- name: Use Node.js ${{ matrix.node }}
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: pnpm
|
||||
cache: pnpm # zizmor: ignore[cache-poisoning] test-only cache
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
@@ -49,12 +67,22 @@ jobs:
|
||||
run: pnpm test
|
||||
|
||||
- name: Plugins Build Test
|
||||
if: ${{ matrix.quality }}
|
||||
run: pnpm build:plugins
|
||||
|
||||
- name: Plugins Type Test
|
||||
if: ${{ matrix.quality }}
|
||||
run: pnpm test:plugins
|
||||
|
||||
- name: Plugins Package Test
|
||||
if: ${{ matrix.quality }}
|
||||
run: pnpm test:plugins:attw
|
||||
|
||||
- name: Lint
|
||||
if: ${{ matrix.quality }}
|
||||
run: pnpm eslint ui && pnpm prettier --check ui
|
||||
|
||||
# https://github.com/arethetypeswrong/arethetypeswrong.github.io
|
||||
- name: "arethetypeswrong test"
|
||||
if: runner.os == 'ubuntu-latest'
|
||||
run: pnpm -F artalk test:attw || true
|
||||
if: ${{ matrix.quality }}
|
||||
run: pnpm -F artalk test:attw
|
||||
@@ -0,0 +1,46 @@
|
||||
name: Test Workflows
|
||||
|
||||
on:
|
||||
push:
|
||||
paths: [".github/**"]
|
||||
branches-ignore: ["release/*", "nightly"]
|
||||
tags-ignore: ["v*"]
|
||||
pull_request:
|
||||
paths: [".github/**"]
|
||||
branches-ignore: ["release/*", "nightly"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: test-workflows-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Validate Actions and security policy
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: false
|
||||
|
||||
- name: Validate workflow syntax
|
||||
run: go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 -color
|
||||
|
||||
- name: Install zizmor
|
||||
run: |
|
||||
python3 -m venv "$RUNNER_TEMP/zizmor"
|
||||
"$RUNNER_TEMP/zizmor/bin/pip" install --disable-pip-version-check zizmor==1.28.0
|
||||
|
||||
- name: Audit workflow security
|
||||
run: |
|
||||
"$RUNNER_TEMP/zizmor/bin/zizmor" --format github .github
|
||||
@@ -69,3 +69,6 @@ node_modules
|
||||
|
||||
playwright-report
|
||||
test-results
|
||||
|
||||
# TypeScript composite build declaration output
|
||||
.tsbuildinfo-dts/
|
||||
+9
-26
@@ -6,11 +6,13 @@
|
||||
# https://goreleaser.com/customization/fury/
|
||||
# https://goreleaser.com/customization/announce/telegram/
|
||||
|
||||
version: 2
|
||||
|
||||
project_name: artalk
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
- CGO_ENABLED=1
|
||||
- CGO_ENABLED=0
|
||||
|
||||
before:
|
||||
hooks:
|
||||
@@ -31,9 +33,6 @@ builds:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
env:
|
||||
- CC=x86_64-linux-gnu-gcc
|
||||
- CXX=x86_64-linux-gnu-g++
|
||||
binary: "{{.ProjectName}}"
|
||||
main: ./main.go
|
||||
ldflags: &common_ldflags |
|
||||
@@ -45,9 +44,6 @@ builds:
|
||||
- linux
|
||||
goarch:
|
||||
- arm64
|
||||
env:
|
||||
- CC=aarch64-linux-gnu-gcc
|
||||
- CXX=aarch64-linux-gnu-g++
|
||||
binary: "{{.ProjectName}}"
|
||||
main: ./main.go
|
||||
ldflags: *common_ldflags
|
||||
@@ -60,9 +56,6 @@ builds:
|
||||
- arm
|
||||
goarm:
|
||||
- "7"
|
||||
env:
|
||||
- CC=arm-linux-gnueabihf-gcc
|
||||
- CXX=arm-linux-gnueabihf-g++
|
||||
binary: "{{.ProjectName}}"
|
||||
main: ./main.go
|
||||
ldflags: *common_ldflags
|
||||
@@ -77,9 +70,6 @@ builds:
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
env:
|
||||
- CC=o64-clang
|
||||
- CXX=o64-clang++
|
||||
binary: "{{.ProjectName}}"
|
||||
main: ./main.go
|
||||
ldflags: *common_ldflags
|
||||
@@ -90,9 +80,6 @@ builds:
|
||||
- darwin
|
||||
goarch:
|
||||
- arm64
|
||||
env:
|
||||
- CC=oa64-clang
|
||||
- CXX=oa64-clang++
|
||||
binary: "{{.ProjectName}}"
|
||||
main: ./main.go
|
||||
ldflags: *common_ldflags
|
||||
@@ -107,9 +94,6 @@ builds:
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
env:
|
||||
- CC=x86_64-w64-mingw32-gcc
|
||||
- CXX=x86_64-w64-mingw32-g++
|
||||
binary: "{{.ProjectName}}"
|
||||
main: ./main.go
|
||||
ldflags: *common_ldflags
|
||||
@@ -125,9 +109,6 @@ builds:
|
||||
- windows
|
||||
goarch:
|
||||
- arm64
|
||||
env:
|
||||
- CC=/llvm-mingw/bin/aarch64-w64-mingw32-gcc
|
||||
- CXX=/llvm-mingw/bin/aarch64-w64-mingw32-g++
|
||||
binary: "{{.ProjectName}}"
|
||||
main: ./main.go
|
||||
ldflags: *common_ldflags
|
||||
@@ -135,7 +116,7 @@ builds:
|
||||
|
||||
archives:
|
||||
- id: artalk
|
||||
builds:
|
||||
ids:
|
||||
- linux-amd64
|
||||
- darwin-amd64
|
||||
- windows-amd64
|
||||
@@ -144,10 +125,12 @@ archives:
|
||||
- linux-arm64
|
||||
- darwin-arm64
|
||||
name_template: "{{.ProjectName}}_v{{.Version}}_{{.Os}}_{{.Arch}}{{.Arm}}"
|
||||
format: tar.gz
|
||||
formats:
|
||||
- tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
formats:
|
||||
- zip
|
||||
wrap_in_directory: true
|
||||
files:
|
||||
- README*
|
||||
@@ -158,7 +141,7 @@ checksum:
|
||||
name_template: "checksums.txt"
|
||||
|
||||
snapshot:
|
||||
name_template: "{{.Version}}-SNAPSHOT-{{.ShortCommit}}"
|
||||
version_template: "{{.Version}}-SNAPSHOT-{{.ShortCommit}}"
|
||||
|
||||
# changelog:
|
||||
# sort: asc
|
||||
|
||||
Vendored
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
// Enable the ESlint flat config support
|
||||
"eslint.experimental.useFlatConfig": true,
|
||||
// Enable the ESLint flat config support (ESLint 9+ / extension v3+)
|
||||
"eslint.useFlatConfig": true,
|
||||
|
||||
// Disable the default formatter, use eslint instead
|
||||
// "prettier.enable": false,
|
||||
|
||||
@@ -1,4 +1,103 @@
|
||||
|
||||
## [v2.10.0](https://github.com/ArtalkJS/Artalk/compare/v2.9.1...v2.10.0) (2026-07-23)
|
||||
|
||||
### Features
|
||||
|
||||
* add 'ugc' to link rel attributes ([#1120](https://github.com/ArtalkJS/Artalk/issues/1120))
|
||||
* **api/vote:** add get vote status endpoint ([#998](https://github.com/ArtalkJS/Artalk/issues/998))
|
||||
* **auth:** add SSO token exchange endpoint ([#1134](https://github.com/ArtalkJS/Artalk/issues/1134))
|
||||
* **config:** enhance config file and data dir retrieval ([#992](https://github.com/ArtalkJS/Artalk/issues/992))
|
||||
* **db:** add TLS support for MySQL with Google Cloud SQL ([#1104](https://github.com/ArtalkJS/Artalk/issues/1104))
|
||||
* **go:** upgrade golang to v1.26.2 and some deps
|
||||
* **go:** upgrade golang to v1.26.5
|
||||
* **plugins:** add plugin registry fetch script
|
||||
* **ui:** add dependency injection container ([#1006](https://github.com/ArtalkJS/Artalk/issues/1006))
|
||||
* **ui:** add npm published version check script
|
||||
* **ui/conf:** add `preferRemoteConf` option ([#1000](https://github.com/ArtalkJS/Artalk/issues/1000)) ([#1011](https://github.com/ArtalkJS/Artalk/issues/1011))
|
||||
* **ui/eslint:** add eslint plugin for artalk ui development ([#995](https://github.com/ArtalkJS/Artalk/issues/995))
|
||||
* **ui/eslint:** update `eslint-plugin-artalk` to v1.0.2 ([#1010](https://github.com/ArtalkJS/Artalk/issues/1010))
|
||||
* **ui/page_vote:** add page voting feature ([#983](https://github.com/ArtalkJS/Artalk/issues/983))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** validate vote status targets
|
||||
* **api:** make vote updates transactional
|
||||
* **api/stats:** exclude pending comments
|
||||
* **api/vote:** return not found for deleted targets
|
||||
* **auth:** require verified email for SSO exchange
|
||||
* **build:** propagate image version metadata
|
||||
* **cache:** return database results on store failure
|
||||
* **ci:** satisfy workflow shell checks
|
||||
* **ci:** harden release supply chain
|
||||
* **ci:** harden release tag handling
|
||||
* **ci:** preserve nightly tag during release refresh
|
||||
* **cmd:** preserve config lookup before data chdir
|
||||
* **config:** preserve keyword file separators
|
||||
* **core:** initialize process timezone once
|
||||
* **db:** enforce Cloud SQL TLS verification
|
||||
* **docs:** support React 19 useRef types
|
||||
* **email:** add Message-ID header
|
||||
* **scripts:** import Buffer explicitly
|
||||
* **scripts:** harden npm publish check
|
||||
* **ui:** preserve generated list response contract
|
||||
* **ui:** restore deprecated context services
|
||||
* **ui:** reject direct context replacement
|
||||
* **ui:** align plugin lifecycle with final config
|
||||
* **ui:** accept scoped comment lists without page data
|
||||
* **ui:** replace vulnerable HTML sanitizer
|
||||
* **ui/editor:** restore deprecated aliases
|
||||
* **ui/sidebar:** handle null array preferences
|
||||
* **ui/types:** preserve atomic config values
|
||||
* **ui/types:** restore deprecated public aliases
|
||||
* **ui/ua:** support OpenHarmony and more browsers ([#1087](https://github.com/ArtalkJS/Artalk/issues/1087))
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* **api/vote:** refactor vote http endpoint and logic ([#997](https://github.com/ArtalkJS/Artalk/issues/997))
|
||||
* **auth:** migrate JWT handling to v5
|
||||
* **ci:** accelerate nightly publishing
|
||||
* **ci:** optimize multi-architecture releases
|
||||
* **ui:** improve modularity, lifecycle, and types ([#1007](https://github.com/ArtalkJS/Artalk/issues/1007))
|
||||
* **ui/auth:** ensure compatibility with new client types ([#1009](https://github.com/ArtalkJS/Artalk/issues/1009))
|
||||
* **ui/comment:** update reply button to use component
|
||||
* **ui/conf:** introduce `DeepPartial` type for ui config ([#996](https://github.com/ArtalkJS/Artalk/issues/996))
|
||||
* **ui/sidebar:** ensure compatibility with new client types ([#1008](https://github.com/ArtalkJS/Artalk/issues/1008))
|
||||
|
||||
### Testing
|
||||
|
||||
* remove races from async tests
|
||||
* **ci:** expand release validation
|
||||
* **server:** verify plugin URL trust filtering
|
||||
* **ui:** update comment e2e flow
|
||||
* **ui:** isolate Playwright end-to-end specs
|
||||
* **ui:** cover Marked 18 integration
|
||||
* **ui:** cover sanitizer attribute allowlist
|
||||
* **ui:** isolate browser storage from Node
|
||||
|
||||
### Documentation
|
||||
|
||||
* document dependency and security updates
|
||||
* add v2.10.0 release notes
|
||||
* add Japanese README ([#1145](https://github.com/ArtalkJS/Artalk/issues/1145))
|
||||
* integrate git changelog plugin
|
||||
* **i18n:** add Turkish (tr-TR) language support ([#1119](https://github.com/ArtalkJS/Artalk/issues/1119))
|
||||
* **i18n:** add `zh-TW` translation for config template
|
||||
* **landing:** redesign landing page ([#1020](https://github.com/ArtalkJS/Artalk/issues/1020))
|
||||
* **landing:** add more translations
|
||||
* **landing:** optimize responsive font size
|
||||
|
||||
### BREAKING CHANGE
|
||||
|
||||
|
||||
Beyond [#997](https://github.com/ArtalkJS/Artalk/issues/997), [#1007](https://github.com/ArtalkJS/Artalk/issues/1007), and [#1011](https://github.com/ArtalkJS/Artalk/issues/1011), v2.10.0 changes default config/data discovery and relative-path semantics; raises source builds to Go 1.26.5; replaces the CGO-backed SQLite driver with a pure-Go implementation; fixes application timezone at process initialization, requiring a full restart to change it; removes the useBackendConf opt-out and remoteConfModifier; changes DI registration, Context mutation, update() return value, and created timing; and requires marked 18 with modern package resolution. Migration guide: https://artalk.js.org/en/guide/releases/v2.10.0.html
|
||||
|
||||
UI client now prioritizes local config in the latest version, whereas previously, the remote config would override it. (See [#1011](https://github.com/ArtalkJS/Artalk/issues/1011))
|
||||
|
||||
This update changes the UI client's export Types. Although we aim to maintain backward compatibility, it may still affect downstream UI programs. Please refer to the [TypeDoc](https://artalk.js.org/typedoc/).
|
||||
|
||||
Vote HTTP API endpoint have been updated. (See [#997](https://github.com/ArtalkJS/Artalk/issues/997))
|
||||
|
||||
|
||||
## [v2.9.1](https://github.com/ArtalkJS/Artalk/compare/v2.9.0...v2.9.1) (2024-09-18)
|
||||
|
||||
### Features
|
||||
|
||||
+5
-12
@@ -24,9 +24,9 @@ cd Artalk
|
||||
|
||||
To develop Artalk, both frontend and backend, install the following tools:
|
||||
|
||||
- [Node.js](https://nodejs.org/en/) (>= 20.17.0)
|
||||
- [PNPM](https://pnpm.io/) (>= 9.10.0)
|
||||
- [Go](https://golang.org/) (>= 1.22)
|
||||
- [Node.js](https://nodejs.org/en/) (>= 22.19.0)
|
||||
- [PNPM](https://pnpm.io/) (>= 10.33.2)
|
||||
- [Go](https://golang.org/) (>= 1.26)
|
||||
- [Docker](https://www.docker.com/) (>= 20.10.0) (optional)
|
||||
- [Docker Compose](https://docs.docker.com/compose/) (>= 1.29.0) (optional)
|
||||
|
||||
@@ -35,30 +35,25 @@ To develop Artalk, both frontend and backend, install the following tools:
|
||||
The development workflow consists of the following steps:
|
||||
|
||||
1. **Set Up Complete Development Instance**:
|
||||
|
||||
- Navigate to the Artalk Repo directory.
|
||||
- Execute `make dev` to run the backend on port `23366`.
|
||||
- Execute `pnpm dev` to run the frontend on port `5173`.
|
||||
- Optionally, execute `pnpm dev:sidebar` to run the sidebar frontend on port `23367`.
|
||||
|
||||
2. **Access Frontend for Development**:
|
||||
|
||||
- Open your browser and go to `http://localhost:5173` to perform your development and testing.
|
||||
|
||||
3. **Testing**:
|
||||
|
||||
- Run the backend unit tests using `make test`.
|
||||
- Run the ui unit tests using `pnpm test`.
|
||||
- Run the ui E2E tests using `make test-frontend-e2e`.
|
||||
|
||||
4. **Building Frontend Code**:
|
||||
|
||||
- When you make changes to the frontend code, build the complete frontend program using `pnpm build:all`.
|
||||
- The JavaScript and CSS code will be found in `ui/artalk/dist`.
|
||||
- The frontend code is automatically deployed to NPM by GitHub Actions.
|
||||
|
||||
5. **Building Backend Code**:
|
||||
|
||||
- When you make changes to the backend code, note that the backend program embeds the frontend code.
|
||||
- Before building the backend, run `make build-frontend` (which runs `scripts/build-frontend.sh`). This will place the embedded frontend main program and sidebar frontend program in `/public`.
|
||||
- Then, build the backend program using `make build`.
|
||||
@@ -102,13 +97,11 @@ Artalk is a monorepo project, meaning all the source code resides in a single re
|
||||
### Run Backend
|
||||
|
||||
1. **Create Configuration File**:
|
||||
|
||||
- Copy `./conf/artalk.example.yml` to the root directory.
|
||||
- Rename it to `artalk.yml`.
|
||||
- Modify the file as needed.
|
||||
|
||||
2. **Start Backend Program**:
|
||||
|
||||
- Run `make dev` to start the backend on port `23366`.
|
||||
- Access it via `http://localhost:23366`.
|
||||
- It's recommended to keep the default port for testing.
|
||||
@@ -124,7 +117,7 @@ If you are using the VSCode, you can use the `F5` key (or RUN button) to start d
|
||||
Execute `git fetch --tags` to get the latest git tag information. This tag will be used as the version number of the backend app. Alternatively, specify it using environment variables: `VERSION="v1.0.0"` and `COMMIT_HASH="66128e"`.
|
||||
|
||||
2. **Build Frontend**:
|
||||
Run `make build-fronted` to build the frontend, as the backend app embeds the frontend code.
|
||||
Run `make build-frontend` to build the frontend, as the backend app embeds the frontend code.
|
||||
|
||||
3. **Build Backend**:
|
||||
Execute `make build` to build the backend program.
|
||||
@@ -224,7 +217,7 @@ Replace `TAG` with the desired tag name (e.g., `latest`).
|
||||
If you have already built the frontend outside the Docker container, you can skip the frontend build process inside the container to speed up the build. Use the following command:
|
||||
|
||||
```sh
|
||||
docker build --build-arg SKIP_FRONTEND_BUILD=true -t artalk:latest .
|
||||
docker build --build-arg SKIP_UI_BUILD=true -t artalk:latest .
|
||||
```
|
||||
|
||||
For more details, refer to the `Dockerfile`.
|
||||
|
||||
+6
-6
@@ -1,11 +1,11 @@
|
||||
### build Artalk
|
||||
FROM golang:1.22.7-alpine3.20 AS builder
|
||||
FROM golang:1.26.5-alpine3.24 AS builder
|
||||
|
||||
WORKDIR /source
|
||||
|
||||
# install tools
|
||||
RUN set -ex \
|
||||
&& apk add --no-cache make git gcc musl-dev bash
|
||||
&& apk add --no-cache make git bash
|
||||
|
||||
# download go deps
|
||||
# (cache by separating the downloading of deps)
|
||||
@@ -22,7 +22,7 @@ ARG SKIP_UI_BUILD=false
|
||||
RUN set -ex \
|
||||
&& if [ "$SKIP_UI_BUILD" = "false" ]; then \
|
||||
apk add --no-cache nodejs npm \
|
||||
&& npm install -g pnpm@9.10.0 \
|
||||
&& npm install -g pnpm@10.33.2 \
|
||||
;fi
|
||||
|
||||
RUN set -ex \
|
||||
@@ -35,12 +35,12 @@ ARG APP_VERSION=""
|
||||
ARG APP_COMMIT_HASH=""
|
||||
|
||||
RUN set -ex \
|
||||
&& if [[ ! -z "$APP_VERSION" ]]; then export VERSION=$APP_VERSION ;fi \
|
||||
&& if [[ ! -z "$APP_COMMIT_HASH" ]]; then export COMMIT_HASH=$APP_COMMIT_HASH ;fi \
|
||||
&& if [[ -n "$APP_VERSION" ]]; then export VERSION="$APP_VERSION" ;fi \
|
||||
&& if [[ -n "$APP_COMMIT_HASH" ]]; then export COMMIT_HASH="$APP_COMMIT_HASH" ;fi \
|
||||
&& make build
|
||||
|
||||
### build final image
|
||||
FROM alpine:3.20
|
||||
FROM alpine:3.24
|
||||
|
||||
# we set the timezone `Asia/Shanghai` by default, you can be modified
|
||||
# by `docker build --build-arg="TZ=Other_Timezone ..."`
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
PKG_NAME := github.com/artalkjs/artalk/v2
|
||||
BIN_NAME := ./bin/artalk
|
||||
LDFLAGS := -s -w
|
||||
|
||||
ifneq ($(strip $(VERSION)),)
|
||||
LDFLAGS += -X '$(PKG_NAME)/internal/config.Version=$(VERSION)'
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(COMMIT_HASH)),)
|
||||
LDFLAGS += -X '$(PKG_NAME)/internal/config.buildCommitHash=$(COMMIT_HASH)'
|
||||
endif
|
||||
|
||||
HAS_RICHGO := $(shell which richgo)
|
||||
GOTEST ?= $(if $(HAS_RICHGO), richgo test, go test)
|
||||
ARGS ?= server
|
||||
|
||||
export CGO_ENABLED := 1
|
||||
export CGO_ENABLED := 0
|
||||
|
||||
all: install build
|
||||
|
||||
@@ -17,7 +26,7 @@ run: all
|
||||
|
||||
build:
|
||||
go build \
|
||||
-ldflags "-s -w" \
|
||||
-ldflags "$(LDFLAGS)" \
|
||||
-o $(BIN_NAME) \
|
||||
$(PKG_NAME)
|
||||
|
||||
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
<p align="center">
|
||||
<img src="https://user-images.githubusercontent.com/22412567/171680920-6e74b77c-c565-487b-bff1-4f94976ecbe7.png" alt="Artalk" width="100%">
|
||||
</p>
|
||||
|
||||
# Artalk
|
||||
|
||||
[](https://www.npmjs.com/package/artalk)
|
||||
[](https://www.npmjs.com/package/artalk)
|
||||
[](https://hub.docker.com/r/artalk/artalk-go)
|
||||
[](https://pkg.go.dev/github.com/artalkjs/artalk/v2)
|
||||
[](https://goreportcard.com/report/github.com/ArtalkJS/Artalk)
|
||||
[](https://circleci.com/gh/ArtalkJS/Artalk/tree/master)
|
||||
[](https://codecov.io/gh/ArtalkJS/Artalk)
|
||||
[](https://bundlephobia.com/package/artalk)
|
||||
|
||||
[ホームページ](https://artalk.js.org) • [ドキュメント](https://artalk.js.org/en/guide/deploy.html) • [最新リリース](https://github.com/ArtalkJS/Artalk/releases) • [変更履歴](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md) • [English](./README.md) • [简体中文](./README.zh.md)
|
||||
|
||||
Artalk は直感的かつ高機能なコメントシステムで、あらゆるブログ、ウェブサイト、ウェブアプリケーションにすぐに導入できます。
|
||||
|
||||
- 🍃 クライアントは約 40KB、純粋な Vanilla JS で作られており、フレームワークに依存しません
|
||||
- 🍱 サーバーは Golang を採用し、効率的かつ軽量なクロスプラットフォーム性能を提供します
|
||||
- 🐳 Docker によるワンクリックデプロイで、手軽かつ高速に導入できます
|
||||
- 🌈 オープンソースソフトウェアで、プライバシーを最優先にセルフホストできます
|
||||
|
||||
## 機能
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<!-- features -->
|
||||
* [サイドバー](https://artalk.js.org/guide/frontend/sidebar.html): 素早い管理と直感的な閲覧
|
||||
* [ソーシャルログイン](https://artalk.js.org/guide/frontend/auth.html): ソーシャルアカウントによる高速ログイン
|
||||
* [メール通知](https://artalk.js.org/guide/backend/email.html): 多様な送信方法とメールテンプレート
|
||||
* [多様なプッシュ通知](https://artalk.js.org/guide/backend/admin_notify.html): 複数のプッシュ方法と通知テンプレート
|
||||
* [サイト内通知](https://artalk.js.org/guide/frontend/sidebar.html): 未読マークとメンション一覧
|
||||
* [キャプチャ](https://artalk.js.org/guide/backend/captcha.html): 多様な認証タイプと頻度制限
|
||||
* [コメントモデレーション](https://artalk.js.org/guide/backend/moderator.html): コンテンツ検出とスパム遮断
|
||||
* [画像アップロード](https://artalk.js.org/guide/backend/img-upload.html): カスタムアップロード、画像ホスティング対応
|
||||
* [Markdown](https://artalk.js.org/guide/intro.html): Markdown 構文に対応
|
||||
* [絵文字パック](https://artalk.js.org/guide/frontend/emoticons.html): OwO 互換、簡単に統合
|
||||
* [マルチサイト](https://artalk.js.org/guide/backend/multi-site.html): サイトの分離と一元管理
|
||||
* [管理者](https://artalk.js.org/guide/backend/multi-site.html): パスワード認証とバッジ識別
|
||||
* [ページ管理](https://artalk.js.org/guide/frontend/sidebar.html): 素早い閲覧とワンクリックでのタイトル遷移
|
||||
* [ページビュー統計](https://artalk.js.org/guide/frontend/pv.html): ページビューを簡単に追跡
|
||||
* [階層構造](https://artalk.js.org/guide/frontend/config.html#nestmax): ネストされたページネーション一覧と無限スクロール
|
||||
* [コメント投票](https://artalk.js.org/guide/frontend/config.html#vote): コメントの賛成・反対投票
|
||||
* [コメント並び替え](https://artalk.js.org/guide/frontend/config.html#listsort): 多様な並び替えオプションを自由に選択
|
||||
* [コメント検索](https://artalk.js.org/guide/frontend/sidebar.html): コメント内容を素早く検索
|
||||
* [コメントのピン留め](https://artalk.js.org/guide/frontend/sidebar.html): 重要なメッセージをピン留め
|
||||
* [作成者のみ表示](https://artalk.js.org/guide/frontend/config.html): 作成者のコメントのみを表示
|
||||
* [コメントジャンプ](https://artalk.js.org/guide/intro.html): 引用されたコメントへ素早く移動
|
||||
* [自動保存](https://artalk.js.org/guide/frontend/config.html): 入力内容の消失を防止
|
||||
* [IP 地域表示](https://artalk.js.org/guide/frontend/ip-region.html): ユーザーの IP 地域を表示
|
||||
* [データ移行](https://artalk.js.org/guide/transfer.html): 自由な移行と素早いバックアップ
|
||||
* [画像ライトボックス](https://artalk.js.org/guide/frontend/lightbox.html): 画像ライトボックスを簡単に統合
|
||||
* [画像の遅延読み込み](https://artalk.js.org/guide/frontend/img-lazy-load.html): 画像を遅延読み込みし、体験を最適化
|
||||
* [LaTeX](https://artalk.js.org/guide/frontend/latex.html): LaTeX 数式の解析を統合
|
||||
* [ナイトモード](https://artalk.js.org/guide/frontend/config.html#darkmode): ナイトモードへの切り替え
|
||||
* [拡張プラグイン](https://artalk.js.org/develop/plugin.html): さらなる可能性を創出
|
||||
* [多言語対応](https://artalk.js.org/guide/frontend/i18n.html): 複数の言語を切り替え
|
||||
* [コマンドライン](https://artalk.js.org/guide/backend/config.html): コマンドラインによる操作管理
|
||||
* [API ドキュメント](https://artalk.js.org/http-api.html): OpenAPI 形式のドキュメントを提供
|
||||
* [プログラムのアップグレード](https://artalk.js.org/guide/backend/update.html): バージョンチェックとワンクリックアップグレード
|
||||
<!-- /features -->
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## インストール
|
||||
|
||||
Docker でワンステップで Artalk サーバーをデプロイ:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name artalk \
|
||||
-p 8080:23366 \
|
||||
-v $(pwd)/data:/data \
|
||||
-e "TZ=America/New_York" \
|
||||
-e "ATK_LOCALE=en" \
|
||||
-e "ATK_SITE_DEFAULT=Artalk Blog" \
|
||||
-e "ATK_SITE_URL=https://example.com" \
|
||||
artalk/artalk-go
|
||||
```
|
||||
|
||||
Artalk クライアントをウェブページに組み込む:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
```ts
|
||||
Artalk.init({
|
||||
el: '#Comments',
|
||||
site: 'Artalk Blog',
|
||||
server: 'https://artalk.example.com',
|
||||
pageKey: '/2018/10/02/hello-world.html'
|
||||
})
|
||||
```
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
バイナリファイル、go install、Linux ディストリビューション向けのパッケージマネージャーなど、さまざまなインストール方法を提供しています。
|
||||
|
||||
[**詳しく見る →**](https://artalk.js.org/en/guide/deploy.html)
|
||||
|
||||
## 開発者の方へ
|
||||
|
||||
プルリクエストを歓迎します!
|
||||
|
||||
コードベースの扱い方、ローカル開発環境のセットアップ、変更の貢献については [開発](https://artalk.js.org/en/develop/) と [コントリビューション](./CONTRIBUTING.md) をご覧ください。
|
||||
|
||||
## コントリビューター
|
||||
|
||||
皆さんの貢献はオープンソースコミュニティを豊かにし、学び、ひらめき、イノベーションを育みます。私たちは皆さんの参加を心から大切にしています。私たちのコミュニティに欠かせない存在でいてくれて、ありがとうございます!🥰
|
||||
|
||||
[](https://github.com/ArtalkJS/Artalk/graphs/contributors)
|
||||
|
||||
## サポーター
|
||||
|
||||
[](https://github.com/ArtalkJS/Artalk/stargazers)
|
||||
|
||||
## Repobeats アナリティクス
|
||||
|
||||

|
||||
|
||||
## Stargazer の推移
|
||||
|
||||
<a href="https://trendshift.io/repositories/6290" target="_blank"><img src="https://trendshift.io/api/badge/repositories/6290" alt="ArtalkJS%2FArtalk | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
|
||||
[](https://starchart.cc/ArtalkJS/Artalk)
|
||||
|
||||
## ライセンス
|
||||
|
||||
[MIT](./LICENSE)
|
||||
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2FArtalkJS%2FArtalk?ref=badge_shield)
|
||||
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2FArtalkJS%2FArtalk?ref=badge_large)
|
||||
@@ -13,7 +13,7 @@
|
||||
[](https://codecov.io/gh/ArtalkJS/Artalk)
|
||||
[](https://bundlephobia.com/package/artalk)
|
||||
|
||||
[Homepage](https://artalk.js.org) • [Documentation](https://artalk.js.org/en/guide/deploy.html) • [Latest Release](https://github.com/ArtalkJS/Artalk/releases) • [Changelog](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md) • [简体中文](./README.zh.md)
|
||||
[Homepage](https://artalk.js.org) • [Documentation](https://artalk.js.org/en/guide/deploy.html) • [Latest Release](https://github.com/ArtalkJS/Artalk/releases) • [Changelog](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md) • [简体中文](./README.zh.md) • [日本語](./README.ja.md)
|
||||
|
||||
Artalk is an intuitive yet feature-rich comment system, ready for immediate deployment into any blog, website, or web application.
|
||||
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
[](https://codecov.io/gh/ArtalkJS/Artalk)
|
||||
[](https://bundlephobia.com/package/artalk)
|
||||
|
||||
[官方网站](https://artalk.js.org) • [最新版本](https://github.com/ArtalkJS/Artalk/releases) • [更新日志](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md) • [English](./README.md)
|
||||
[官方网站](https://artalk.js.org) • [最新版本](https://github.com/ArtalkJS/Artalk/releases) • [更新日志](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md) • [English](./README.md) • [日本語](./README.ja.md)
|
||||
|
||||
Artalk 是一款简单易用但功能丰富的评论系统,你可以开箱即用地部署并置入任何博客、网站、Web 应用。
|
||||
|
||||
|
||||
+29
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
@@ -67,6 +68,13 @@ func New() *ArtalkCmd {
|
||||
// Parse base flags
|
||||
cmd.eagerParseFlags()
|
||||
|
||||
// Resolve config from the invocation directory before an implicit data-dir chdir.
|
||||
if cfgFile, err := resolveConfigFileBeforeDataDir(cmd.cfgFile, cmd.workDir); err == nil {
|
||||
cmd.cfgFile = cfgFile
|
||||
} else {
|
||||
log.Fatal("Config path fail: ", err)
|
||||
}
|
||||
|
||||
// Init data directory (work directory)
|
||||
if workDir, err := initDataDir(cmd.workDir); err == nil {
|
||||
cmd.workDir = workDir
|
||||
@@ -77,6 +85,27 @@ func New() *ArtalkCmd {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func resolveConfigFileBeforeDataDir(cfgFile string, workDir string) (string, error) {
|
||||
// An explicit work directory keeps the historical behavior: relative config
|
||||
// paths and default config discovery are resolved after changing to workDir.
|
||||
if workDir != "" {
|
||||
return cfgFile, nil
|
||||
}
|
||||
|
||||
if cfgFile == "" {
|
||||
cfgFile = config.RetrieveConfigFile()
|
||||
}
|
||||
if cfgFile == "" || filepath.IsAbs(cfgFile) {
|
||||
return cfgFile, nil
|
||||
}
|
||||
|
||||
absPath, err := filepath.Abs(cfgFile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("resolve config file %q: %w", cfgFile, err)
|
||||
}
|
||||
return absPath, nil
|
||||
}
|
||||
|
||||
// Parses the global app flags before calling atk.RootCmd.Execute().
|
||||
// so we can have all flags ready for use on initialization.
|
||||
func (atk *ArtalkCmd) eagerParseFlags() error {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestResolveConfigFileBeforeDataDir(t *testing.T) {
|
||||
originalDir, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
originalDataHome := xdg.DataHome
|
||||
defer func() {
|
||||
require.NoError(t, os.Chdir(originalDir))
|
||||
xdg.DataHome = originalDataHome
|
||||
}()
|
||||
|
||||
invocationDir := t.TempDir()
|
||||
dataHome := t.TempDir()
|
||||
invocationDir, err = filepath.EvalSymlinks(invocationDir)
|
||||
require.NoError(t, err)
|
||||
dataHome, err = filepath.EvalSymlinks(dataHome)
|
||||
require.NoError(t, err)
|
||||
dataDir := filepath.Join(dataHome, "artalk")
|
||||
require.NoError(t, os.Mkdir(dataDir, 0o755))
|
||||
require.NoError(t, os.WriteFile(
|
||||
filepath.Join(invocationDir, "artalk.yml"),
|
||||
[]byte("timezone: UTC\n"),
|
||||
0o600,
|
||||
))
|
||||
require.NoError(t, os.Chdir(invocationDir))
|
||||
xdg.DataHome = dataHome
|
||||
|
||||
cfgFile, err := resolveConfigFileBeforeDataDir("", "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, filepath.Join(invocationDir, "artalk.yml"), cfgFile)
|
||||
|
||||
workDir, err := initDataDir("")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, dataDir, workDir)
|
||||
|
||||
currentDir, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, dataDir, currentDir)
|
||||
|
||||
conf, err := getConfig(cfgFile)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "UTC", conf.TimeZone)
|
||||
}
|
||||
|
||||
func TestResolveConfigFileWithExplicitPaths(t *testing.T) {
|
||||
originalDir, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
defer func() { require.NoError(t, os.Chdir(originalDir)) }()
|
||||
|
||||
invocationDir := t.TempDir()
|
||||
invocationDir, err = filepath.EvalSymlinks(invocationDir)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, os.Chdir(invocationDir))
|
||||
|
||||
cfgFile, err := resolveConfigFileBeforeDataDir("custom.yml", "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, filepath.Join(invocationDir, "custom.yml"), cfgFile)
|
||||
|
||||
cfgFile, err = resolveConfigFileBeforeDataDir("custom.yml", "./runtime")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "custom.yml", cfgFile)
|
||||
}
|
||||
@@ -220,6 +220,9 @@ auth:
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
domain: ""
|
||||
sso:
|
||||
enabled: false
|
||||
issuer: ""
|
||||
frontend:
|
||||
placeholder: ""
|
||||
noComment: ""
|
||||
|
||||
@@ -0,0 +1,456 @@
|
||||
# Dinleme adresi
|
||||
host: "0.0.0.0"
|
||||
|
||||
# Dinleme portu
|
||||
port: 23366
|
||||
|
||||
# Uygulama Anahtarı (JWT üretimi için)
|
||||
app_key: ""
|
||||
|
||||
# Hata ayıklama modu
|
||||
debug: false
|
||||
|
||||
# Dil (Unicode BCP 47 formatında) ["en", "zh-CN", "zh-TW", "ja", "fr", "ko", "ru", "tr"]
|
||||
# -- bkz. https://www.techonthenet.com/js/language_tags.php --
|
||||
locale: tr
|
||||
|
||||
# Zaman dilimi (IANA Zaman Dilimi Veritabanı formatında)
|
||||
# -- bkz. https://en.wikipedia.org/wiki/List_of_tz_database_time_zones --
|
||||
timezone: Europe/Istanbul
|
||||
|
||||
# Varsayılan site adı (uygulama ilk başlatıldığında oluşturulur)
|
||||
site_default: Varsayılan Site
|
||||
|
||||
# Varsayılan site URL'si
|
||||
site_url: ""
|
||||
|
||||
# Giriş zaman aşımı (saniye cinsinden)
|
||||
login_timeout: 259200
|
||||
|
||||
# Veritabanı
|
||||
db:
|
||||
# Veritabanı türü ["sqlite", "mysql", "pgsql", "mssql"]
|
||||
type: sqlite
|
||||
# Veritabanı dosyası (yalnızca SQLite için)
|
||||
file: ./data/artalk.db
|
||||
# Tablo öneki (örn. "atk_")
|
||||
table_prefix: ""
|
||||
# -- Aşağıdakiler SQLite için gerekli değildir --
|
||||
# Veritabanı adı
|
||||
name: artalk
|
||||
# Ana makine adresi
|
||||
host: localhost
|
||||
# Ana makine portu
|
||||
port: 3306
|
||||
# Veritabanı kullanıcısı
|
||||
user: root
|
||||
# Veritabanı şifresi
|
||||
password: ""
|
||||
# Veritabanı karakter seti
|
||||
charset: utf8mb4
|
||||
# SSL modunu etkinleştir
|
||||
ssl: false
|
||||
# Hazır İfadeler (Prepared Statement)
|
||||
prepare_stmt: true
|
||||
|
||||
# Web sunucusu
|
||||
http:
|
||||
# Gövde boyutu sınırı (birim: MB)
|
||||
body_limit: 100
|
||||
# Proxy Başlığı (güvenilir bir ters proxy veya CDN arkasındaysa kullanıcının gerçek IP'sini almak için `X-Forwarded-For` doldurun)
|
||||
proxy_header: ""
|
||||
|
||||
# Günlük Kaydı (Logging)
|
||||
log:
|
||||
# Günlük kaydını etkinleştir
|
||||
enabled: true
|
||||
# Günlük dosyası yolu
|
||||
filename: ./data/artalk.log
|
||||
|
||||
# Önbellek (Cache)
|
||||
cache:
|
||||
# Önbelleği etkinleştir
|
||||
enabled: false
|
||||
# Önbellek türü ["redis", "memcache", "builtin"]
|
||||
type: builtin
|
||||
# Önbellek geçerlilik süresi (dakika cinsinden)
|
||||
expires: 30
|
||||
# Önbellek ön yükleme (program başladığında önbelleği ısıtır)
|
||||
warm_up: false
|
||||
# -- Aşağıdakiler `builtin` (yerleşik) önbellek için gerekli değildir --
|
||||
# Önbellek sunucu adresi (örn. "localhost:6379")
|
||||
server: ""
|
||||
# Redis yapılandırması
|
||||
redis:
|
||||
# Bağlantı türü ["tcp", "unix"]
|
||||
network: tcp
|
||||
# Redis kullanıcı adı
|
||||
username: ""
|
||||
# Redis şifresi
|
||||
password: ""
|
||||
# Redis veritabanı numarası (örn. 0)
|
||||
db: 0
|
||||
|
||||
# Güvenilir alan adları
|
||||
# -- örn. ["https://artalk.example.com:23366"] sitenizin URL'sini buraya ekleyin --
|
||||
trusted_domains: []
|
||||
|
||||
# SSL
|
||||
ssl:
|
||||
# SSL'i etkinleştir
|
||||
enabled: false
|
||||
# Sertifika dosyası yolu
|
||||
# (örn. "/etc/letsencrypt/live/example.com/fullchain.pem")
|
||||
cert_path: ""
|
||||
# Anahtar dosyası yolu
|
||||
# (örn. "/etc/letsencrypt/live/example.com/privkey.pem")
|
||||
key_path: ""
|
||||
|
||||
# Moderatör
|
||||
# -- Yorumların herkese açık olmadan önce incelenmesi --
|
||||
moderator:
|
||||
# Varsayılan bekleme durumu (yeni yorumların yönetici tarafından onaylanması gerekir)
|
||||
pending_default: false
|
||||
# API isteği başarısız olduğunda engelle (API isteği başarısız olduğunda yorumların geçmesine izin vermek için false yapın)
|
||||
api_fail_block: false
|
||||
# Akismet Anahtarı
|
||||
# (Akismet spam önleme servisi, https://akismet.com)
|
||||
akismet_key: ""
|
||||
# Tencent Bulut İçerik Güvenliği
|
||||
# (Tencent Bulut İçerik Güvenliği ile yorumları otomatik inceleyin)
|
||||
# -- bkz. https://cloud.tencent.com/document/product/1124/64508 --
|
||||
tencent:
|
||||
enabled: false
|
||||
secret_id: ""
|
||||
secret_key: ""
|
||||
region: ap-guangzhou
|
||||
# Aliyun İçerik Güvenliği
|
||||
# (Aliyun İçerik Güvenliği ile yorumları otomatik inceleyin)
|
||||
# -- bkz. https://help.aliyun.com/document_detail/28417.html --
|
||||
aliyun:
|
||||
enabled: false
|
||||
access_key_id: ""
|
||||
access_key_secret: ""
|
||||
region: cn-shanghai
|
||||
# Kelime filtresi (yerel çevrimdışı sözlük)
|
||||
keywords:
|
||||
# Kelime filtresini etkinleştir
|
||||
enabled: false
|
||||
# Eşleşme olduğunda beklemeye al
|
||||
pending: false
|
||||
# Sözlük dosyası (birden fazla sözlük dosyasını destekler)
|
||||
files:
|
||||
- ./data/keywords_1.txt
|
||||
file_sep: "\n"
|
||||
replace_to: x
|
||||
|
||||
# Doğrulama (Captcha)
|
||||
captcha:
|
||||
# Doğrulamayı etkinleştir
|
||||
enabled: true
|
||||
# Her zaman doğrulama gerekli
|
||||
always: false
|
||||
# Doğrulama türü ["image", "turnstile", "recaptcha", "hcaptcha", "geetest"]
|
||||
captcha_type: image
|
||||
# İşlem sınırı
|
||||
# (doğrulamayı etkinleştirmek için gereken işlem sayısı)
|
||||
action_limit: 3
|
||||
# Sıfırlama Zaman Aşımı (işlem sayacını sıfırlama süresi. birim: saniye, devre dışı bırakmak için -1 yapın)
|
||||
action_reset: 60
|
||||
# Turnstile
|
||||
# (https://www.cloudflare.com/products/turnstile/)
|
||||
turnstile:
|
||||
site_key: ""
|
||||
secret_key: ""
|
||||
# reCaptcha
|
||||
# (https://www.google.com/recaptcha/about/)
|
||||
recaptcha:
|
||||
site_key: ""
|
||||
secret_key: ""
|
||||
# hCaptcha (https://www.hcaptcha.com/)
|
||||
hcaptcha:
|
||||
site_key: ""
|
||||
secret_key: ""
|
||||
# Geetest (https://www.geetest.com)
|
||||
geetest:
|
||||
captcha_id: ""
|
||||
captcha_key: ""
|
||||
|
||||
# Yükleme
|
||||
img_upload:
|
||||
# Resim yüklemeyi etkinleştir
|
||||
enabled: true
|
||||
# Resim depolama yolu
|
||||
path: ./data/artalk-img/
|
||||
# Resim boyutu sınırı (birim: MB)
|
||||
max_size: 5
|
||||
# Resim bağlantısı temel yolu (varsayılan: "/static/images/")
|
||||
public_path: null
|
||||
# Upgit yapılandırması
|
||||
upgit:
|
||||
# Upgit'i etkinleştir
|
||||
enabled: false
|
||||
# Komut satırı argümanları
|
||||
exec: upgit -c UPGIT_CONF_FILE_PATH -t /artalk-img
|
||||
# Yükleme başarılı olduktan sonra yerel resmi sil
|
||||
del_local: true
|
||||
|
||||
# E-posta
|
||||
email:
|
||||
# E-posta bildirimini etkinleştir
|
||||
enabled: false
|
||||
# Gönderim yöntemi ["smtp", "ali_dm", "sendmail"]
|
||||
send_type: smtp
|
||||
# Gönderen takma adı
|
||||
send_name: "{{reply_nick}}"
|
||||
# Gönderen e-posta adresi
|
||||
send_addr: noreply@example.com
|
||||
# E-posta konusu
|
||||
mail_subject: "[{{site_name}}] @{{reply_nick}} tarafından bir yanıt aldınız"
|
||||
# E-posta şablon dosyası (özel şablon kullanmak için dosya yolunu ayarlayın)
|
||||
mail_tpl: default
|
||||
# SMTP gönderimi (etkinleştirmek için gönderim yöntemini "smtp" yapın)
|
||||
smtp:
|
||||
# Gönderen e-posta sunucusu
|
||||
host: smtp.qq.com
|
||||
# E-posta portu
|
||||
port: 587
|
||||
# Kullanıcı adı
|
||||
username: example@qq.com
|
||||
# Şifre
|
||||
password: ""
|
||||
# Aliyun posta push
|
||||
# (etkinleştirmek için gönderim yöntemini "ali_dm" yapın; bkz: https://help.aliyun.com/document_detail/29444.html)
|
||||
ali_dm:
|
||||
access_key_id: ""
|
||||
access_key_secret: ""
|
||||
account_name: noreply@example.com
|
||||
|
||||
# Çoklu Push
|
||||
admin_notify:
|
||||
# Bildirim şablonu (özel şablon kullanmak için dosya yolunu ayarlayın)
|
||||
notify_tpl: default
|
||||
# Bekleyen yorumlar için hala bildirim gönder (yorumlar engellendiğinde bile bildirimler gönderilir)
|
||||
notify_pending: false
|
||||
# Gürültü modu
|
||||
# -- gürültü modu varsayılan olarak devre dışıdır. --
|
||||
# -- Bu seçenek `false` olarak ayarlandığında, yalnızca yöneticiye gönderilen mesajlar bildirilir, --
|
||||
# -- "kullanıcı A"nın "kullanıcı B"ye yanıt vermesi gibi iki kullanıcı arasındaki iletişim yöneticiye bildirilmez. --
|
||||
noise_mode: false
|
||||
# Yöneticiyi bildir
|
||||
email:
|
||||
# Etkinleştir (diğer push yöntemlerini kullanırken devre dışı bırakılabilir)
|
||||
enabled: true
|
||||
# E-posta konusu (yöneticiye gönderilen e-posta konusu)
|
||||
mail_subject: '[{{site_name}}] "{{page_title}}" sayfası yeni bir yoruma sahip'
|
||||
# Yönetici e-posta şablon dosyası (özel şablon kullanmak için dosya yolunu ayarlayın)
|
||||
mail_tpl: ""
|
||||
# Telegram
|
||||
telegram:
|
||||
enabled: false
|
||||
api_token: ""
|
||||
receivers:
|
||||
- 7777777
|
||||
# Bark
|
||||
bark:
|
||||
enabled: false
|
||||
server: http://day.app/xxxxxxx/
|
||||
# Lark
|
||||
lark:
|
||||
enabled: false
|
||||
webhook_url: ""
|
||||
# Mesaj türü ["text", "card"]
|
||||
msg_type: "text"
|
||||
# WebHook
|
||||
webhook:
|
||||
enabled: false
|
||||
url: ""
|
||||
# DingTalk
|
||||
ding_talk:
|
||||
enabled: false
|
||||
token: ""
|
||||
secret: ""
|
||||
# Slack
|
||||
slack:
|
||||
enabled: false
|
||||
oauth_token: ""
|
||||
receivers:
|
||||
- "CHANNEL_ID"
|
||||
# LINE
|
||||
line:
|
||||
enabled: false
|
||||
channel_secret: ""
|
||||
channel_access_token: ""
|
||||
receivers:
|
||||
- USER_ID_1
|
||||
- GROUP_ID_1
|
||||
|
||||
# Sosyal Giriş
|
||||
auth:
|
||||
# Sosyal Girişi Etkinleştir
|
||||
enabled: false
|
||||
# Anonim yorum yapmaya izin ver (Doğrulamayı atlamaya izin ver, yalnızca anonim bir takma ad ve e-posta doldurmak yeterlidir)
|
||||
anonymous: false
|
||||
# Geri dönüş URL'si (https://example.com/api/v2/auth/{provider}/callback)
|
||||
callback: "http://localhost:23366/api/v2/auth/{provider}/callback"
|
||||
# E-posta
|
||||
email:
|
||||
# E-posta şifre girişini etkinleştir
|
||||
enabled: true
|
||||
# Doğrulama e-postası konusu
|
||||
verify_subject: "Kodunuz - {{code}}"
|
||||
# Doğrulama e-postası şablonu (özel şablon kullanmak için dosya yolunu ayarlayın)
|
||||
verify_tpl: default
|
||||
# GitHub
|
||||
github:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# GitLab
|
||||
gitlab:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Gitea
|
||||
gitea:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Google
|
||||
google:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Mastodon
|
||||
mastodon:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Twitter
|
||||
twitter:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Facebook
|
||||
facebook:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Discord
|
||||
discord:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Steam
|
||||
steam:
|
||||
enabled: false
|
||||
api_key: ""
|
||||
# Apple
|
||||
apple:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Microsoft
|
||||
microsoft:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# WeChat
|
||||
wechat:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Tiktok
|
||||
tiktok:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Slack
|
||||
slack:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Line
|
||||
line:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Patreon
|
||||
patreon:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Auth0
|
||||
auth0:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
domain: ""
|
||||
# SSO (Harici OIDC erişim belirteçlerini Artalk oturumuyla değiştirir, IdP'nin /userinfo uç noktası ile doğrulanır)
|
||||
# -- Gömülü uygulama zaten OIDC çalıştırıyorsa ve Artalk'ın kendi açılır giriş ekranını göstermeden bu oturumu devralmasını istiyorsanız kullanın. --
|
||||
sso:
|
||||
# SSO belirteç değişimini etkinleştir
|
||||
enabled: false
|
||||
# OIDC issuer URL'si (örn. "tenant.auth0.com" veya "https://tenant.auth0.com")
|
||||
issuer: ""
|
||||
|
||||
# Kullanıcı Arayüzü Ayarları
|
||||
frontend:
|
||||
# Yorum kutusu yer tutucu metni
|
||||
placeholder: ""
|
||||
# Yorum olmadığında görüntülenecek metin
|
||||
noComment: ""
|
||||
# Gönder düğmesi metni
|
||||
sendBtn: ""
|
||||
# Hareketli yorum kutusu
|
||||
editorTravel: true
|
||||
# İfadeler (Emoticonlar)
|
||||
emoticons: https://cdn.jsdelivr.net/gh/ArtalkJS/Emoticons/grps/default.json
|
||||
# Oy verme düğmesi
|
||||
vote: true
|
||||
# Beğenmeme düğmesi
|
||||
voteDown: false
|
||||
# Kullanıcı UA rozeti
|
||||
uaBadge: false
|
||||
# Yorum sıralama
|
||||
listSort: true
|
||||
# Editör gerçek zamanlı önizleme
|
||||
preview: true
|
||||
# Düzleştirme modu ["auto", true, false]
|
||||
flatMode: auto
|
||||
# Karanlık mod ["inherit", "auto"]
|
||||
darkMode: inherit
|
||||
# Maksimum yuvalama seviyesi
|
||||
nestMax: 2
|
||||
# Yuvalama yorum sıralama kuralları ["DATE_ASC", "DATE_DESC", "VOTE_UP_DESC"]
|
||||
nestSort: DATE_ASC
|
||||
# Gravatar
|
||||
gravatar:
|
||||
# API URL'si
|
||||
mirror: https://www.gravatar.com/avatar/
|
||||
# API parametreleri
|
||||
params: sha256=1&d=mp&s=240
|
||||
# Yorum sayfalama
|
||||
pagination:
|
||||
# Sayfa başına yorum sayısı
|
||||
pageSize: 20
|
||||
# Daha fazla yükle modu (sayfalama çubuğunu kullanmak için devre dışı bırakın)
|
||||
readMore: true
|
||||
# Kaydırmalı yükleme
|
||||
autoLoad: true
|
||||
# İçerik yükseklik sınırı
|
||||
heightLimit:
|
||||
# Yorum içeriği yükseklik sınırı (birim: px)
|
||||
content: 300
|
||||
# Alt yorum alanı yükseklik sınırı (birim: px)
|
||||
children: 400
|
||||
# Kaydırılabilir (yükseklik sınırı alanı kaydırılabilir olsun)
|
||||
scrollable: false
|
||||
# Resim geç yükleme [false, "native", "data-src"]
|
||||
imgLazyLoad: false
|
||||
# İstek zaman aşımı (birim: ms)
|
||||
reqTimeout: 15000
|
||||
# Sürüm kontrolü
|
||||
versionCheck: true
|
||||
# Eklentiler
|
||||
pluginURLs: []
|
||||
@@ -10,7 +10,7 @@ app_key: ""
|
||||
# Debug mode
|
||||
debug: false
|
||||
|
||||
# Language (follow Unicode BCP 47) ["en", "zh-CN", "zh-TW", "ja", "fr", "ko", "ru"]
|
||||
# Language (follow Unicode BCP 47) ["en", "zh-CN", "zh-TW", "ja", "fr", "ko", "ru", "tr"]
|
||||
# -- see https://www.techonthenet.com/js/language_tags.php --
|
||||
locale: en
|
||||
|
||||
@@ -386,6 +386,13 @@ auth:
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
domain: ""
|
||||
# SSO (Exchange external OIDC access tokens for Artalk login sessions, verified via the IdP's /userinfo endpoint)
|
||||
# -- Use this when the embedding app already runs OIDC and you want Artalk to inherit that session without its own popup login UI. --
|
||||
sso:
|
||||
# Enable SSO token exchange
|
||||
enabled: false
|
||||
# OIDC issuer URL (e.g. "tenant.auth0.com" or "https://tenant.auth0.com")
|
||||
issuer: ""
|
||||
|
||||
# UI Settings
|
||||
frontend:
|
||||
|
||||
@@ -394,6 +394,13 @@ auth:
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
domain: ""
|
||||
# SSO (用外部 OIDC 身份提供商的访问令牌换取 Artalk 登录会话,通过调用 IdP 的 /userinfo 端点校验)
|
||||
# -- 当嵌入页面已运行 OIDC 且希望 Artalk 复用该登录态、不显示自身弹窗登录界面时使用。 --
|
||||
sso:
|
||||
# 启用 SSO 令牌交换
|
||||
enabled: false
|
||||
# OIDC issuer 地址 (如 "tenant.auth0.com" 或 "https://tenant.auth0.com")
|
||||
issuer: ""
|
||||
|
||||
# 界面配置
|
||||
frontend:
|
||||
|
||||
@@ -394,6 +394,13 @@ auth:
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
domain: ""
|
||||
# SSO (用外部 OIDC 身分提供商的存取權杖換取 Artalk 登入工作階段,透過呼叫 IdP 的 /userinfo 端點驗證)
|
||||
# -- 當嵌入頁面已執行 OIDC 且希望 Artalk 沿用該登入狀態、不顯示自身彈窗登入介面時使用。 --
|
||||
sso:
|
||||
# 啟用 SSO 權杖交換
|
||||
enabled: false
|
||||
# OIDC issuer 位址 (如 "tenant.auth0.com" 或 "https://tenant.auth0.com")
|
||||
issuer: ""
|
||||
|
||||
# 介面配置
|
||||
frontend:
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
.vitepress/dist/**
|
||||
.vitepress/cache/**
|
||||
.vitepress/.temp/**
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"latest": "2.9.1"
|
||||
"latest": "2.10.0"
|
||||
}
|
||||
@@ -105,7 +105,7 @@ import Artalk from 'artalk'
|
||||
|
||||
const ArtalkComment = () => {
|
||||
const { pathname } = useLocation()
|
||||
const artalk = useRef<Artalk>()
|
||||
const artalk = useRef<Artalk | null>(null)
|
||||
|
||||
const handleContainerInit = useCallback(
|
||||
(node: HTMLDivElement | null) => {
|
||||
@@ -114,7 +114,7 @@ const ArtalkComment = () => {
|
||||
}
|
||||
if (artalk.current) {
|
||||
artalk.current.destroy()
|
||||
artalk.current = undefined
|
||||
artalk.current = null
|
||||
}
|
||||
artalk.current = Artalk.init({
|
||||
el: node,
|
||||
|
||||
@@ -283,6 +283,16 @@ timezone: Asia/Shanghai
|
||||
|
||||
Fill in the time zone where you are located, corresponding to the IANA database time zone name, refer to: [Wikipedia](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) / [RFC-6557](https://www.rfc-editor.org/rfc/rfc6557.html).
|
||||
|
||||
`timezone` controls the timezone used by the Artalk application. It may differ from the host system or container `TZ` setting. The same configuration can be supplied through `ATK_TIMEZONE`:
|
||||
|
||||
```bash
|
||||
ATK_TIMEZONE=America/New_York artalk server
|
||||
```
|
||||
|
||||
::: warning Restart required
|
||||
The application timezone is a process-level fixed value and is set only when the Artalk process initializes. It cannot be changed dynamically at runtime. After changing it in the configuration file, environment, or Dashboard, fully restart the Artalk process or container for the new value to take effect.
|
||||
:::
|
||||
|
||||
```
|
||||
UTC+08:00 Asia/Shanghai
|
||||
UTC+09:00 Asia/Tokyo
|
||||
|
||||
@@ -109,6 +109,7 @@ moderator:
|
||||
- **pending**: Whether to set the comment to pending review status upon successful match.
|
||||
- **files**: Keyword files. Multiple files are allowed, and Artalk will merge the keyword libraries upon startup.
|
||||
- **file_sep**: Keyword file content separator. For example, if each line in the file contains a keyword, set this item to `\n`.
|
||||
The value cannot be empty. The dashboard displays line feeds, carriage returns, and tabs as `\n`, `\r`, and `\t`. Enter `\\n` when the separator must be the literal characters `\n`.
|
||||
- **replace_to**: Replacement character. For example, if this item is set to `x`, you can set `pending` to `false`, allowing the comment to pass review automatically, but the matched keywords will be replaced with `x`, resulting in `fxxk` or `xxxx`.
|
||||
|
||||
Note: It is recommended not to use `*` asterisk as `replace_to` because it conflicts with the Markdown bold syntax.
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
## Update Changes
|
||||
|
||||
For significant changes, such as updates that require modifications to existing configurations, please refer to the version update log for relevant prompts: [CHANGELOG.md](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md).
|
||||
For significant changes, such as updates that require modifications to existing configurations, refer to the release notes and migration instructions:
|
||||
|
||||
- [v2.10.0 Release Notes and Migration Guide](../releases/v2.10.0.md)
|
||||
- [Complete CHANGELOG.md](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md)
|
||||
|
||||
## One-Click Upgrade via Command Line
|
||||
|
||||
|
||||
+20
-15
@@ -25,6 +25,7 @@ services:
|
||||
- ./data:/data
|
||||
environment:
|
||||
- TZ=America/New_York
|
||||
- ATK_TIMEZONE=America/New_York
|
||||
- ATK_LOCALE=en
|
||||
- ATK_SITE_DEFAULT=Artalk's Blog
|
||||
- ATK_SITE_URL=https://example.com
|
||||
@@ -36,6 +37,8 @@ services:
|
||||
- ATK_ADMIN_USERS_0_BADGE_COLOR=#0083FF
|
||||
```
|
||||
|
||||
`TZ` controls the container or host process environment, while `ATK_TIMEZONE` controls the Artalk application timezone; the two values may differ. The Artalk timezone is applied only during process initialization, so fully restart the Artalk process or container after changing `ATK_TIMEZONE`.
|
||||
|
||||
When the variable is an array, set array values using space-separated strings or numerical indices, for example:
|
||||
|
||||
```
|
||||
@@ -66,14 +69,14 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
<div class="env-table">
|
||||
<!-- env-variables -->
|
||||
|
||||
## 通用配置
|
||||
## General
|
||||
|
||||
| 环境变量 | 默认值 | 描述 | 路径 |
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_APP_KEY** | `""` | App Key (for generation of JWT) | app_key (App Key) |
|
||||
| **ATK_DEBUG** | `false` | Debug mode | debug (Debug mode) |
|
||||
| **ATK_HOST** | `"0.0.0.0"` | Listen host | host (Listen host) |
|
||||
| **ATK_LOCALE** | `"en"` | Language (follow Unicode BCP 47) (可选:`["en", "zh-CN", "zh-TW", "ja", "fr", "ko", "ru"]`) | locale (Language) |
|
||||
| **ATK_LOCALE** | `"en"` | Language (follow Unicode BCP 47) (可选:`["en", "zh-CN", "zh-TW", "ja", "fr", "ko", "ru", "tr"]`) | locale (Language) |
|
||||
| **ATK_LOGIN_TIMEOUT** | `259200` | Login timeout (in seconds) | login_timeout (Login timeout) |
|
||||
| **ATK_PORT** | `23366` | Listen port | port (Listen port) |
|
||||
| **ATK_SITE_DEFAULT** | `"Default Site"` | Default site name (create when app is first launched) | site_default (Default site name) |
|
||||
@@ -84,7 +87,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## Multi-Push
|
||||
|
||||
| 环境变量 | 默认值 | 描述 | 路径 |
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_ADMIN_NOTIFY_BARK_ENABLED** | `false` | 启用 | admin_notify.bark.enabled (Multi-Push > Bark > Enabled) |
|
||||
| **ATK_ADMIN_NOTIFY_BARK_SERVER** | `"http://day.app/xxxxxxx/"` | Server | admin_notify.bark.server (Multi-Push > Bark > Server) |
|
||||
@@ -116,7 +119,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## Social Login
|
||||
|
||||
| 环境变量 | 默认值 | 描述 | 路径 |
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_AUTH_ANONYMOUS** | `false` | Allow anonymous commenting (Allow skipping verification, only fill in an anonymous nickname and email) | auth.anonymous (Social Login > Allow anonymous commenting) |
|
||||
| **ATK_AUTH_APPLE_CLIENT_ID** | `""` | ClientId | auth.apple.client_id (Social Login > Apple > ClientId) |
|
||||
@@ -164,6 +167,8 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
| **ATK_AUTH_SLACK_CLIENT_ID** | `""` | ClientId | auth.slack.client_id (Social Login > Slack > ClientId) |
|
||||
| **ATK_AUTH_SLACK_CLIENT_SECRET** | `""` | ClientSecret | auth.slack.client_secret (Social Login > Slack > ClientSecret) |
|
||||
| **ATK_AUTH_SLACK_ENABLED** | `false` | 启用 | auth.slack.enabled (Social Login > Slack > Enabled) |
|
||||
| **ATK_AUTH_SSO_ENABLED** | `false` | Enable SSO token exchange | auth.sso.enabled (Social Login > SSO > Enable SSO token exchange) |
|
||||
| **ATK_AUTH_SSO_ISSUER** | `""` | OIDC issuer URL (e.g. "tenant.auth0.com" or "https://tenant.auth0.com") | auth.sso.issuer (Social Login > SSO > OIDC issuer URL) |
|
||||
| **ATK_AUTH_STEAM_API_KEY** | `""` | ApiKey | auth.steam.api_key (Social Login > Steam > ApiKey) |
|
||||
| **ATK_AUTH_STEAM_ENABLED** | `false` | 启用 | auth.steam.enabled (Social Login > Steam > Enabled) |
|
||||
| **ATK_AUTH_TIKTOK_CLIENT_ID** | `""` | ClientId | auth.tiktok.client_id (Social Login > Tiktok > ClientId) |
|
||||
@@ -179,7 +184,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## Cache
|
||||
|
||||
| 环境变量 | 默认值 | 描述 | 路径 |
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_CACHE_ENABLED** | `false` | Enable cache | cache.enabled (Cache > Enable cache) |
|
||||
| **ATK_CACHE_EXPIRES** | `30` | Cache expiration time (in minutes) | cache.expires (Cache > Cache expiration time) |
|
||||
@@ -194,7 +199,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## Captcha
|
||||
|
||||
| 环境变量 | 默认值 | 描述 | 路径 |
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_CAPTCHA_ACTION_LIMIT** | `3` | Action limit (the number of actions required to activate captcha) | captcha.action_limit (Captcha > Action limit) |
|
||||
| **ATK_CAPTCHA_ACTION_RESET** | `60` | Reset Timeout (timeout to reset action counter. unit: s, set to -1 to disable) | captcha.action_reset (Captcha > Reset Timeout) |
|
||||
@@ -213,7 +218,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## Database
|
||||
|
||||
| 环境变量 | 默认值 | 描述 | 路径 |
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_DB_CHARSET** | `"utf8mb4"` | Database charset | db.charset (Database > Database charset) |
|
||||
| **ATK_DB_FILE** | `"./data/artalk.db"` | Database file (only for SQLite) | db.file (Database > Database file) |
|
||||
@@ -230,7 +235,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## Email
|
||||
|
||||
| 环境变量 | 默认值 | 描述 | 路径 |
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_EMAIL_ALI_DM_ACCESS_KEY_ID** | `""` | AccessKeyId | email.ali_dm.access_key_id (Email > Aliyun mail push > AccessKeyId) |
|
||||
| **ATK_EMAIL_ALI_DM_ACCESS_KEY_SECRET** | `""` | AccessKeySecret | email.ali_dm.access_key_secret (Email > Aliyun mail push > AccessKeySecret) |
|
||||
@@ -249,7 +254,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## UI Settings
|
||||
|
||||
| 环境变量 | 默认值 | 描述 | 路径 |
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_FRONTEND_DARKMODE** | `"inherit"` | Dark mode (可选:`["inherit", "auto"]`) | frontend.darkMode (UI Settings > Dark mode) |
|
||||
| **ATK_FRONTEND_EDITORTRAVEL** | `true` | Movable comment box | frontend.editorTravel (UI Settings > Movable comment box) |
|
||||
@@ -281,7 +286,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## Web server
|
||||
|
||||
| 环境变量 | 默认值 | 描述 | 路径 |
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_HTTP_BODY_LIMIT** | `100` | Body size limit (unit: MB) | http.body_limit (Web server > Body size limit) |
|
||||
| **ATK_HTTP_PROXY_HEADER** | `""` | Proxy Header (fill `X-Forwarded-For` to get user real IP if behind a trusted reverse proxy or CDN) | http.proxy_header (Web server > Proxy Header) |
|
||||
@@ -289,7 +294,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## Upload
|
||||
|
||||
| 环境变量 | 默认值 | 描述 | 路径 |
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_IMG_UPLOAD_ENABLED** | `true` | Enable image upload | img_upload.enabled (Upload > Enable image upload) |
|
||||
| **ATK_IMG_UPLOAD_MAX_SIZE** | `5` | Image size limit (unit: MB) | img_upload.max_size (Upload > Image size limit) |
|
||||
@@ -302,7 +307,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## Logging
|
||||
|
||||
| 环境变量 | 默认值 | 描述 | 路径 |
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_LOG_ENABLED** | `true` | Enable logging | log.enabled (Logging > Enable logging) |
|
||||
| **ATK_LOG_FILENAME** | `"./data/artalk.log"` | Log file path | log.filename (Logging > Log file path) |
|
||||
@@ -310,7 +315,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## Moderator
|
||||
|
||||
| 环境变量 | 默认值 | 描述 | 路径 |
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_MODERATOR_AKISMET_KEY** | `""` | Akismet Key (Akismet anti-spam service, https://akismet.com) | moderator.akismet_key (Moderator > Akismet Key) |
|
||||
| **ATK_MODERATOR_ALIYUN_ACCESS_KEY_ID** | `""` | AccessKeyId | moderator.aliyun.access_key_id (Moderator > Aliyun Content Security > AccessKeyId) |
|
||||
@@ -332,7 +337,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## SSL
|
||||
|
||||
| 环境变量 | 默认值 | 描述 | 路径 |
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_SSL_CERT_PATH** | `""` | Certificate file path (e.g. "/etc/letsencrypt/live/example.com/fullchain.pem") | ssl.cert_path (SSL > Certificate file path) |
|
||||
| **ATK_SSL_ENABLED** | `false` | Enable SSL | ssl.enabled (SSL > Enable SSL) |
|
||||
|
||||
@@ -68,6 +68,62 @@ If only one login method is enabled, such as GitHub login, the GitHub authorizat
|
||||
|
||||
For integrating GitHub login, refer to the documentation: [About Creating GitHub Apps](https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app). After obtaining the Client ID and Client Secret, fill them in the "GitHub" option in the social login settings page of the Artalk Dashboard.
|
||||
|
||||
## SSO Token Exchange
|
||||
|
||||
If your site is embedded in an application that already authenticates users through an external OIDC identity provider (Auth0, Keycloak, Okta, etc.), you can let Artalk reuse that session instead of showing its own login UI. The surrounding application exchanges the IdP access token it already holds for an Artalk session token, so users who are already signed in to the parent app can comment without an extra click or popup.
|
||||
|
||||
This feature is **disabled by default** and is opt-in only. Enable it through the [configuration file](../backend/config.md) or [environment variables](../env.md#social-login), filling in the OIDC issuer of your provider:
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
enabled: true
|
||||
sso:
|
||||
enabled: true
|
||||
issuer: "https://tenant.auth0.com" # e.g. "tenant.auth0.com" or "https://tenant.auth0.com"
|
||||
```
|
||||
|
||||
Once enabled, the surrounding application posts the IdP access token to the exchange endpoint:
|
||||
|
||||
```
|
||||
POST /api/v2/sso/exchange
|
||||
Content-Type: application/json
|
||||
|
||||
{ "token": "<external IdP access token>" }
|
||||
```
|
||||
|
||||
Artalk validates the token by calling the issuer's `/userinfo` endpoint (the standard OIDC way — the IdP verifies and signs the response server-side, so no key handling is required on Artalk's side) and reads the `email` claim. Artalk only finds or creates the matching user when the response also contains `email_verified: true`, then returns the same response shape as the other login endpoints:
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "<Artalk session token>",
|
||||
"user": { "name": "...", "email": "...", "is_admin": false }
|
||||
}
|
||||
```
|
||||
|
||||
The frontend writes this into `localStorage["ArtalkUser"]` before `Artalk.init()` runs, and the widget then treats the user as fully logged in (no popup, and no admin password prompt for admin users):
|
||||
|
||||
```js
|
||||
const res = await fetch('https://comments.example.com/api/v2/sso/exchange', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ token: idpAccessToken }), // the access token from your IdP
|
||||
})
|
||||
const { token, user } = await res.json()
|
||||
|
||||
// Pre-populate the Artalk session before the widget mounts
|
||||
localStorage.setItem('ArtalkUser', JSON.stringify({ ...user, token }))
|
||||
|
||||
Artalk.init({ el: '#Comments', server: 'https://comments.example.com', site: 'Blog' })
|
||||
```
|
||||
|
||||
The endpoint returns `404` when SSO is not enabled, `401` when the IdP rejects the token or the email is unverified, and `400` when the token carries no email claim. Artalk does not provide a switch to bypass email verification.
|
||||
|
||||
::: tip
|
||||
|
||||
Validation relies entirely on the issuer's `/userinfo` rejecting invalid or revoked tokens, and the user is matched by the `email` claim. Only enable this for an issuer you trust. Because an unverified email could impersonate an existing user or administrator, Artalk requires `email_verified: true`.
|
||||
|
||||
:::
|
||||
|
||||
## Plugin Development
|
||||
|
||||
The social login feature of Artalk is implemented through an independent plugin developed using Solid.js. The code can be found in [@ArtalkJS/Artalk:ui/plugin-auth](https://github.com/ArtalkJS/Artalk/tree/master/ui/plugin-auth).
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
# Artalk v2.10.0 Release Notes and Migration Guide
|
||||
|
||||
v2.10.0 is the next stable release after v2.9.1. It adds page voting, SSO token exchange, MySQL Cloud SQL TLS, and CGO-free SQLite builds, while reorganizing the UI client's lifecycle, configuration, and type system.
|
||||
|
||||
::: warning Read before upgrading
|
||||
v2.10.0 contains intentional API and behavior changes. Upgrade the server and UI client together. Themes, plugins, direct HTTP API consumers, and projects importing Artalk TypeScript types should complete the migrations below first.
|
||||
:::
|
||||
|
||||
## Upgrade checklist
|
||||
|
||||
- Back up the database, configuration files, and custom frontend assets.
|
||||
- For binary deployments, verify the working directory and every relative file path; pass `-w` and an absolute `-c` path to retain the previous behavior.
|
||||
- Upgrade source-build and packaging environments to Go 1.26.5.
|
||||
- To change the application timezone, set `timezone` or `ATK_TIMEZONE` and fully restart Artalk.
|
||||
- Upgrade the server and UI client together to avoid mixing the old and new Vote APIs.
|
||||
- Review local-versus-remote UI configuration priority; local configuration is now preferred by default.
|
||||
- Search for `useBackendConf`, `remoteConfModifier`, two-argument `ctx.inject()`, assignments to `ctx.conf` / `ctx.$root`, and `artalk.update(...).xxx()` chains.
|
||||
- Rebuild themes and plugins that import Artalk types or internal UI services.
|
||||
- Use a modern Node.js toolchain; the Artalk repository development baseline is Node.js 22.19 or later.
|
||||
|
||||
## Highlights
|
||||
|
||||
### UI and interaction
|
||||
|
||||
- Added [page voting](../frontend/voting.md#page-voting) and the ability to query the current visitor's vote state ([#983](https://github.com/ArtalkJS/Artalk/issues/983), [#998](https://github.com/ArtalkJS/Artalk/issues/998)).
|
||||
- Added `preferRemoteConf` to make local-versus-remote UI configuration precedence explicit ([#1000](https://github.com/ArtalkJS/Artalk/issues/1000), [#1011](https://github.com/ArtalkJS/Artalk/issues/1011)).
|
||||
- Introduced a dependency injection container and reorganized UI modules, lifecycle handling, and public types ([#1006](https://github.com/ArtalkJS/Artalk/issues/1006), [#1007](https://github.com/ArtalkJS/Artalk/issues/1007)).
|
||||
- Added the Artalk UI ESLint plugin and updated it to v1.0.2 ([#995](https://github.com/ArtalkJS/Artalk/issues/995), [#1010](https://github.com/ArtalkJS/Artalk/issues/1010)).
|
||||
- Extended User-Agent detection for OpenHarmony and more browsers ([#1087](https://github.com/ArtalkJS/Artalk/issues/1087)).
|
||||
- Added `ugc` to comment-link `rel` attributes ([#1120](https://github.com/ArtalkJS/Artalk/issues/1120)).
|
||||
|
||||
### Server and API
|
||||
|
||||
- Added `POST /api/v2/sso/exchange` to exchange an access token from a trusted OIDC identity provider for an Artalk session token ([#1134](https://github.com/ArtalkJS/Artalk/issues/1134)). It is disabled by default; configure only a trusted HTTPS issuer in production, and require `/userinfo` to return `email_verified: true`.
|
||||
- Unified the Vote API around a target type, target ID, and choice, and added a vote-status endpoint.
|
||||
- Improved configuration-file and data-directory discovery while preserving the located configuration before changing the data directory ([#992](https://github.com/ArtalkJS/Artalk/issues/992)).
|
||||
- Added mutual TLS configuration for MySQL on Google Cloud SQL ([#1104](https://github.com/ArtalkJS/Artalk/issues/1104)).
|
||||
|
||||
### Builds and runtime
|
||||
|
||||
- SQLite now uses a pure-Go driver. Stable builds no longer require CGO or a separate `GORELEASER_CROSS` toolchain, so standard Go tooling can produce cross-platform artifacts directly.
|
||||
- Upgraded the Go toolchain to v1.26.5 and updated related dependencies, including the maintained v5 JWT implementation used for login sessions. Existing HS256 token claims and signatures remain wire-compatible, so this server upgrade does not itself force users to sign in again.
|
||||
- Updated the official container build and runtime bases to Alpine 3.24. The Go builder remains pinned to v1.26.5, and container data-directory and configuration paths are unchanged.
|
||||
- Updated the frontend and documentation toolchain, including Vite 8.1, Marked 18.0.7, and KaTeX 0.17. The `@artalk/plugin-katex` peer range still accepts KaTeX 0.16.45 through 0.17.x, so existing themes are not forced to replace KaTeX as part of this upgrade.
|
||||
- Added scripts for fetching the plugin registry and checking versions already published to npm.
|
||||
- npm packages use modern conditional exports; legacy Node.js 10 module resolution is no longer supported.
|
||||
|
||||
### Documentation and localization
|
||||
|
||||
- Added Turkish (`tr-TR`) and a Traditional Chinese configuration template.
|
||||
- Added a Japanese README.
|
||||
- Redesigned the landing page, added translations, and improved responsive font sizing.
|
||||
- Integrated Git Changelog into the documentation site.
|
||||
|
||||
## Security and reliability
|
||||
|
||||
- The backend JWT parser now uses `github.com/golang-jwt/jwt/v5`, explicitly allows only HS256, continues to validate expiration, and has regression coverage for legacy token format, signing algorithms, and expiration behavior.
|
||||
- The Markdown HTML sanitizer now uses the maintained `DOMPurify` instead of the unpatched, ReDoS-affected `insane` parser. Artalk retains its previous tag, attribute, class, style, and URL-scheme allowlists and adds malicious-markup and ReDoS regression tests.
|
||||
- The frontend lockfile pins patched transitive versions of `fast-uri`, `brace-expansion`, `js-yaml`, `undici`, Vite, and other build and development tooling; the complete workspace dependency audit reports no known vulnerabilities.
|
||||
- Google Cloud SQL TLS now loads the server CA, client certificate, and client key together, verifies the server certificate and hostname, and requires TLS 1.2 or later. All three certificate paths must be configured together so partial TLS configuration cannot be silently ignored.
|
||||
- The Vote API rejects malformed, non-positive, missing, or unknown targets. Vote records and target counters are updated in one database transaction and roll back together on failure.
|
||||
- Plugin URLs are filtered by the server: relative and trusted URLs may be sent to the client, while untrusted absolute URLs are removed.
|
||||
- Plugin-registry downloads require HTTPS and approved hosts and have bounded redirects, response size, and request time.
|
||||
- The npm version check no longer interpolates package names into a shell command and reports network or parsing errors as failures.
|
||||
- Process timezone initialization now happens once instead of mutating global timezone state during concurrent requests.
|
||||
|
||||
## Breaking changes and migration
|
||||
|
||||
### 1. Local UI configuration is preferred by default
|
||||
|
||||
In v2.9.x, remote configuration overrode values passed to `Artalk.init()`. v2.10.0 prefers local configuration by default:
|
||||
|
||||
```text
|
||||
Artalk.init() local config > environment variables > Dashboard = configuration file
|
||||
```
|
||||
|
||||
No additional option is needed for the new default:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
server: 'https://comments.example.com',
|
||||
site: 'My Site',
|
||||
locale: 'en', // the local value is retained
|
||||
})
|
||||
```
|
||||
|
||||
Explicitly enable remote-first behavior when required:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
server: 'https://comments.example.com',
|
||||
site: 'My Site',
|
||||
preferRemoteConf: true,
|
||||
})
|
||||
```
|
||||
|
||||
`useBackendConf` is deprecated and is always treated as `true`. Setting it to `false` no longer prevents the client from reading configuration that the backend must provide; it is not a replacement for `preferRemoteConf`.
|
||||
|
||||
### 2. `remoteConfModifier` has been removed
|
||||
|
||||
`remoteConfModifier` is no longer part of the public configuration. Migrate according to its purpose:
|
||||
|
||||
- Put fixed overrides directly in `Artalk.init()`; local values win by default.
|
||||
- Use `preferRemoteConf: true` when Dashboard values should win.
|
||||
- Use `fetchCommentsOnInit` to control the initial comment request.
|
||||
- Listen for `created` and call `update()` for post-initialization changes.
|
||||
|
||||
```js
|
||||
const artalk = Artalk.init({
|
||||
server: 'https://comments.example.com',
|
||||
site: 'My Site',
|
||||
fetchCommentsOnInit: false,
|
||||
})
|
||||
|
||||
artalk.on('created', () => {
|
||||
artalk.update({ locale: 'en' })
|
||||
artalk.reload()
|
||||
})
|
||||
```
|
||||
|
||||
There is no equivalent hook for arbitrarily modifying remote configuration before plugin initialization.
|
||||
|
||||
### 3. Configuration types distinguish resolved values from inputs
|
||||
|
||||
`Config` represents a fully resolved configuration. `ConfigPartial` is the recursively optional input accepted by `Artalk.init()`, `update()`, and plugin configuration updates.
|
||||
|
||||
```ts
|
||||
import type { Config, ConfigPartial } from 'artalk'
|
||||
|
||||
function createComments(conf: ConfigPartial) {
|
||||
return Artalk.init(conf)
|
||||
}
|
||||
|
||||
function readResolvedConfig(conf: Config) {
|
||||
console.log(conf.server, conf.pagination.pageSize)
|
||||
}
|
||||
```
|
||||
|
||||
Code that used `Partial<ArtalkConfig>` for initialization should use `ConfigPartial`. `ArtalkConfig` remains importable but now represents the complete configuration.
|
||||
|
||||
`ConfigPartial` preserves functions, DOM elements, `Date`, `RegExp`, and array-element semantics instead of incorrectly expanding those values into recursively partial object fields.
|
||||
|
||||
### 4. DI registration and resolution are separate
|
||||
|
||||
The old two-argument `ctx.inject(key, value)` registration form has been removed. Register with `provide()` and resolve with `inject()`:
|
||||
|
||||
```ts
|
||||
// v2.9.x
|
||||
ctx.inject('example', createExample())
|
||||
const example = ctx.get('example')
|
||||
|
||||
// v2.10.0
|
||||
ctx.provide('example', () => createExample())
|
||||
const example = ctx.inject('example')
|
||||
```
|
||||
|
||||
`provide()` supports dependency lists and `singleton` / `transient` lifecycles. Custom TypeScript service keys should extend `Services` through declaration merging.
|
||||
|
||||
### 5. `ctx.conf` and `ctx.$root` cannot be replaced
|
||||
|
||||
Both remain readable, but assigning a new object or element throws an error with migration guidance:
|
||||
|
||||
```ts
|
||||
// Incorrect
|
||||
ctx.conf = nextConf
|
||||
ctx.$root = nextElement
|
||||
|
||||
// Correct
|
||||
ctx.updateConf(nextConf)
|
||||
const conf = ctx.getConf()
|
||||
const root = ctx.getEl()
|
||||
```
|
||||
|
||||
To change the root element, destroy the old instance and create a new instance with a different `el`.
|
||||
|
||||
### 6. `Artalk.update()` returns `void`
|
||||
|
||||
`update()` no longer returns the Artalk instance. Split chained calls:
|
||||
|
||||
```js
|
||||
// v2.9.x
|
||||
artalk.update({ pageKey: '/next' }).reload()
|
||||
|
||||
// v2.10.0
|
||||
artalk.update({ pageKey: '/next' })
|
||||
artalk.reload()
|
||||
```
|
||||
|
||||
### 7. Plugin initialization and the `created` event
|
||||
|
||||
Built-in, local, and network plugins now initialize after local and remote configuration have been merged. Network plugins no longer observe a temporary configuration from only one source.
|
||||
|
||||
`created` fires after every plugin has initialized, followed by `mounted`. Plugins that depended on the old order should perform pre-initialization work in the plugin function and use a `created` listener for work requiring the complete plugin environment.
|
||||
|
||||
### 8. Vote HTTP API and client methods
|
||||
|
||||
Old vote endpoint:
|
||||
|
||||
```http
|
||||
POST /api/v2/votes/comment_up/123
|
||||
```
|
||||
|
||||
New endpoints:
|
||||
|
||||
```http
|
||||
GET /api/v2/votes/comment/123
|
||||
POST /api/v2/votes/comment/123/up
|
||||
```
|
||||
|
||||
`target_name` must be `comment` or `page`; `choice` must be `up` or `down`.
|
||||
|
||||
The generated client methods changed accordingly:
|
||||
|
||||
```ts
|
||||
await api.votes.getVote('comment', 123)
|
||||
await api.votes.createVote('comment', 123, 'up', { name, email })
|
||||
```
|
||||
|
||||
The old endpoint and `votes.vote()` method do not have compatibility adapters.
|
||||
|
||||
### 9. SQLite now uses a pure-Go driver
|
||||
|
||||
The server SQLite driver has changed from the CGO implementation based on `mattn/go-sqlite3` to the pure-Go implementation provided by `github.com/libtnb/sqlite`. Stable builds no longer require a local C compiler, `CGO_ENABLED=1`, or the cross-compilation toolchain selected through `GORELEASER_CROSS_VERSION`.
|
||||
|
||||
The SQLite on-disk format has not changed. Existing databases configured through `db.file` can be used directly, so standard deployments do not require a database configuration change. Back up the database before upgrading as usual; Artalk will run its normal schema migrations on the first v2.10.0 startup.
|
||||
|
||||
Projects that build Artalk from source or embed it as a Go module should note the following:
|
||||
|
||||
- C compiler, CGO, and Goreleaser Cross configuration used only for SQLite can be removed.
|
||||
- Do not rely on `mattn/go-sqlite3`-specific build tags, DSN parameters, C APIs, or dynamic extension-loading behavior.
|
||||
- Re-run migration, concurrent read/write, and backup/restore tests when using custom SQLite compile options, extensions, or native backup integrations.
|
||||
|
||||
This change does not affect deployments using MySQL, PostgreSQL, or MSSQL.
|
||||
|
||||
### 10. Default configuration and data directories follow XDG rules
|
||||
|
||||
Without `--workdir` / `-w`, Artalk no longer always uses the invocation directory as its working directory. v2.10.0 uses the following discovery order:
|
||||
|
||||
- Configuration files: the invocation directory, its `data` subdirectory, `$XDG_CONFIG_HOME/artalk`, then `/etc/artalk`.
|
||||
- Data directory: keep the previous working directory when `./data` exists; otherwise use `$XDG_DATA_HOME/artalk`, then `/var/lib/artalk`, or create `$XDG_DATA_HOME/artalk` if neither exists.
|
||||
|
||||
The configuration file is located before an implicit data-directory change. Relative database, log, and upload paths in that configuration—as well as relative file arguments passed to commands such as `import`, `export`, and `gen`—are still resolved from the final working directory.
|
||||
|
||||
Existing deployments with a `./data` directory keep the previous behavior. The official Docker image also passes an explicit working directory and configuration path, so it is not affected.
|
||||
|
||||
To preserve the previous path semantics for a binary deployment, specify both paths explicitly:
|
||||
|
||||
```bash
|
||||
artalk -w /srv/artalk -c /srv/artalk/artalk.yml server
|
||||
```
|
||||
|
||||
Before upgrading, verify every relative path including `db.file`, `log.filename`, `img_upload.path`, IP-region databases, and keyword files to avoid creating an empty database or files in the newly selected data directory.
|
||||
|
||||
### 11. Source builds require Go 1.26.5
|
||||
|
||||
The minimum version in `go.mod` has increased from Go 1.22.7 to Go 1.26.5. This affects `go install`, source builds, distribution packaging, and downstream projects that include Artalk in a Go build.
|
||||
|
||||
Users of official binaries and Docker images do not need to install Go. Source-build environments must use Go 1.26.5. CI systems that disable automatic Go Toolchain downloads or operate offline must install the matching toolchain in advance.
|
||||
|
||||
### 12. The application timezone is fixed at process initialization
|
||||
|
||||
The `timezone` configuration and `ATK_TIMEZONE` environment variable remain available for an Artalk application timezone that is independent of the host system or container `TZ` setting:
|
||||
|
||||
```yaml
|
||||
timezone: America/New_York
|
||||
```
|
||||
|
||||
```bash
|
||||
ATK_TIMEZONE=America/New_York artalk server
|
||||
```
|
||||
|
||||
Timezone is process-global state. v2.10.0 sets it once during Artalk process initialization and does not support changing it dynamically at runtime. After changing it in the configuration file, environment, or Dashboard, fully restart the Artalk process or container; using Dashboard “Apply settings” alone is not sufficient to switch timezone.
|
||||
|
||||
### 13. `marked` 18 and modern package entry points
|
||||
|
||||
The default Artalk build continues to include Markdown support. ArtalkLite does not bundle `marked`; downstream consumers must provide a compatible `marked@^18.0.3`.
|
||||
|
||||
Artalk's use of `Marked`, `Renderer`, `parse()`, `setOptions()`, `use()`, and tokenizer extensions does not rely on v18-only APIs. Upstream parsing details changed between v14 and v18, however, so projects with custom renderers, tokenizers, or output snapshots should rerun their Markdown tests.
|
||||
|
||||
Node.js 10 and toolchains that rely on legacy package resolution are no longer supported. Artalk repository development and builds use Node.js 22.19 or later.
|
||||
|
||||
## Retained compatibility entry points
|
||||
|
||||
The following entry points remain in v2.10.0 to give themes and third-party plugins time to migrate, but are marked `deprecated`:
|
||||
|
||||
| Previous entry point | Preferred entry point |
|
||||
| --- | --- |
|
||||
| `ContextApi` | `Context` |
|
||||
| `DataManagerApi` | `DataManager` |
|
||||
| `EditorApi` | `Editor` |
|
||||
| `ctx.data` | `ctx.getData()` or `ctx.inject('data')` |
|
||||
| `ctx.user` | `ctx.getUser()` or `ctx.inject('user')` |
|
||||
| `ctx.editor` | `ctx.inject('editor')` |
|
||||
| `ctx.list` | `ctx.inject('list')` |
|
||||
| `ctx.layerManager` | `ctx.inject('layers')` |
|
||||
| `ctx.checkerLauncher` | `ctx.inject('checkers')` |
|
||||
| `ctx.sidebarLayer` | `ctx.inject('sidebar')` |
|
||||
| `ctx.editorPlugs` | `ctx.inject('editorPlugs')` |
|
||||
| `editor.$el` | `editor.getEl()` |
|
||||
| `editor.getPlugs()` | `editor.getPlugins()` |
|
||||
| `editor.setReply()` | `editor.setReplyComment()` |
|
||||
|
||||
These aliases are transitional. New code should use the preferred entry points directly. See [TypeDoc](https://artalk.js.org/typedoc/) for the complete public type surface.
|
||||
|
||||
## Other changes
|
||||
|
||||
- The comment reply button now uses a component implementation.
|
||||
- Sidebar and Auth UI have been updated for the new client types.
|
||||
- Added npm-published-version checking and plugin-registry generation workflows.
|
||||
- Pinned the Swagger TypeScript client generator and updated its command so development environments do not receive incompatible versions through `npx`.
|
||||
- Updated the GitHub Actions runtimes used for artifacts, caches, Docker, CodeQL, and Codecov while preserving workflow inputs and release-artifact layout.
|
||||
- Updated frontend, Go, and documentation dependencies and removed obsolete test projects and CI configuration.
|
||||
|
||||
See the complete commit comparison at [`v2.9.1...v2.10.0`](https://github.com/ArtalkJS/Artalk/compare/v2.9.1...v2.10.0).
|
||||
@@ -136,13 +136,16 @@ Go to the [Disqus backend](https://disqus.com/admin), find "Moderation - Export"
|
||||
|
||||
### Commento
|
||||
|
||||
You can export the data file in JSON format from the Commento backend, then use the [conversion tool](#conversion-tool) for conversion.
|
||||
Go to the Commento backend, click on "General - Export" in sequence, export the data file in JSON format, and then use the [conversion tool](#conversion-tool) for conversion.
|
||||
|
||||
[Image, to be supplemented...]
|
||||

|
||||
|
||||
### Twikoo
|
||||
[Twikoo](https://twikoo.js.org/) can obtain the JSON file through the "Export" section in the administrative panel where it is located, and then convert it using the [conversion tool](#conversion-tool).
|
||||
|
||||
[Twikoo](https://twikoo.js.org/) is a comment system developed based on Tencent Cloud. Go to the [Tencent Cloud backend](https://console.cloud.tencent.com/tcb) to export the comment data file in JSON format, then use the [conversion tool](#conversion-tool) for conversion.
|
||||

|
||||
|
||||
In addition, for Twikoo tencent cloud deployment, you can travel to [Tencent Cloud Background](https://console.cloud.tencent.com/tcb) export comment JSON format data.
|
||||
|
||||
<img src="/images/transfer/tencent-tcb.png" style="max-width: 480px;">
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
@@ -15,13 +15,13 @@
|
||||
"serve:docs": "vitepress serve"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@artalk/plugin-katex": "^0.2.4",
|
||||
"@artalk/plugin-lightbox": "^0.2.4",
|
||||
"@nolebase/vitepress-plugin-git-changelog": "^2.6.1",
|
||||
"@artalk/plugin-katex": "workspace:^",
|
||||
"@artalk/plugin-lightbox": "workspace:^",
|
||||
"@nolebase/vitepress-plugin-git-changelog": "^2.18.2",
|
||||
"artalk": "workspace:^",
|
||||
"katex": "^0.16.11",
|
||||
"lightgallery": "^2.7.2",
|
||||
"vitepress": "1.4.1",
|
||||
"vue": "^3.5.12"
|
||||
"katex": "^0.17.0",
|
||||
"lightgallery": "^2.9.0",
|
||||
"vitepress": "1.6.4",
|
||||
"vue": "^3.5.40"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"composite": true,
|
||||
"types": ["vite/client"],
|
||||
"resolveJsonModule": true,
|
||||
"module": "ESNext",
|
||||
|
||||
@@ -24,9 +24,9 @@ cd Artalk
|
||||
|
||||
为了开发 Artalk 的前端和后端,请安装以下工具:
|
||||
|
||||
- [Node.js](https://nodejs.org/en/) (>= 20.17.0)
|
||||
- [PNPM](https://pnpm.io/) (>= 9.10.0)
|
||||
- [Go](https://golang.org/) (>= 1.22)
|
||||
- [Node.js](https://nodejs.org/en/) (>= 22.19.0)
|
||||
- [PNPM](https://pnpm.io/) (>= 10.33.2)
|
||||
- [Go](https://golang.org/) (>= 1.26)
|
||||
- [Docker](https://www.docker.com/) (>= 20.10.0)(可选)
|
||||
- [Docker Compose](https://docs.docker.com/compose/) (>= 1.29.0)(可选)
|
||||
|
||||
@@ -224,7 +224,7 @@ docker build -t artalk:TAG .
|
||||
如果你已经在 Docker 容器外部构建了前端,可以跳过容器内的前端构建过程以加快构建速度。使用以下命令:
|
||||
|
||||
```sh
|
||||
docker build --build-arg SKIP_FRONTEND_BUILD=true -t artalk:latest .
|
||||
docker build --build-arg SKIP_UI_BUILD=true -t artalk:latest .
|
||||
```
|
||||
|
||||
更多详细信息,请参考 `Dockerfile`。
|
||||
|
||||
@@ -105,7 +105,7 @@ import Artalk from 'artalk'
|
||||
|
||||
const ArtalkComment = () => {
|
||||
const { pathname } = useLocation()
|
||||
const artalk = useRef<Artalk>()
|
||||
const artalk = useRef<Artalk | null>(null)
|
||||
|
||||
const handleContainerInit = useCallback(
|
||||
(node: HTMLDivElement | null) => {
|
||||
@@ -114,7 +114,7 @@ const ArtalkComment = () => {
|
||||
}
|
||||
if (artalk.current) {
|
||||
artalk.current.destroy()
|
||||
artalk.current = undefined
|
||||
artalk.current = null
|
||||
}
|
||||
artalk.current = Artalk.init({
|
||||
el: node,
|
||||
|
||||
@@ -0,0 +1,347 @@
|
||||
# 2026-05 依赖升级说明
|
||||
|
||||
本文档记录了本次大规模依赖升级所涉及的全部变更,涵盖前端工具链、ESLint 生态、TypeScript 编译配置、构建工具迁移及相关文档与 CI 的同步更新。
|
||||
|
||||
---
|
||||
|
||||
## 一、前端依赖升级
|
||||
|
||||
### 核心工具链
|
||||
|
||||
| 包 | 旧版本 | 新版本 |
|
||||
|----|--------|--------|
|
||||
| `typescript` | 5.6.3 | 6.0.3 |
|
||||
| `vite` | 5.4.9 | 8.0.10 |
|
||||
| `vitest` | ^2.1.3 | ^4.1.5 |
|
||||
| `rollup` | 4.24.0 | 4.60.3 |
|
||||
| `sass` | 1.80.3 | 1.99.0 |
|
||||
| `terser` | 5.36.0 | 5.46.2 |
|
||||
| `postcss` | 8.4.47 | 8.5.14 |
|
||||
| `autoprefixer` | 10.4.20 | 10.5.0 |
|
||||
| `tsx` | ^4.19.1 | ^4.21.0 |
|
||||
| `cross-env` | ^7.0.3 | ^10.1.0 |
|
||||
| `prettier` | 3.3.3 | 3.8.3 |
|
||||
|
||||
### ESLint 生态
|
||||
|
||||
| 包 | 旧版本 | 新版本 |
|
||||
|----|--------|--------|
|
||||
| `eslint` | ^9.13.0 | ^10.3.0 |
|
||||
| `typescript-eslint` | ^8.10.0 | ^8.59.1 |
|
||||
| `@typescript-eslint/eslint-plugin` | 8.10.0 | 8.59.1 |
|
||||
| `eslint-plugin-vue` | ^9.29.0 | ^10.9.0 |
|
||||
| `eslint-plugin-import-x` | ^4.3.1 | ^4.16.2 |
|
||||
| `eslint-plugin-compat` | ^6.0.1 | ^7.0.2 |
|
||||
| `eslint-plugin-react-hooks` | 5.1.0-beta | 7.1.1 |
|
||||
| `eslint-config-prettier` | 9.1.0 | 10.1.8 |
|
||||
| `eslint-import-resolver-typescript` | 3.6.3 | 4.4.4 |
|
||||
| `vue-eslint-parser` | ^9.4.3 | ^10.4.0 |
|
||||
| `globals` | ^15.11.0 | ^17.6.0 |
|
||||
|
||||
### Vite 插件
|
||||
|
||||
| 包 | 旧版本 | 新版本 |
|
||||
|----|--------|--------|
|
||||
| `vite-plugin-dts` | 4.2.4 | 5.0.0 |
|
||||
| `vite-plugin-checker` | 0.8.0 | 0.13.0 |
|
||||
|
||||
### UI 子包
|
||||
|
||||
| 包 | 子包 | 旧版本 | 新版本 |
|
||||
|----|------|--------|--------|
|
||||
| `marked` | `ui/artalk` (dep + peer) | ^14.1.3 | ^18.0.3 |
|
||||
| `vue` | artalk-sidebar, docs | ^3.5.12 | ^3.5.33 |
|
||||
| `vue-router` | artalk-sidebar | ^4.4.5 | ^5.0.6 |
|
||||
| `vue-i18n` | artalk-sidebar | ^10.0.4 | ^11.4.0 |
|
||||
| `pinia` | artalk-sidebar | ^2.2.4 | ^3.0.4 |
|
||||
| `vue-tsc` | artalk-sidebar | ^2.1.6 | ^3.2.8 |
|
||||
| `@vitejs/plugin-vue` | artalk-sidebar | ^5.1.4 | ^6.0.6 |
|
||||
| `unplugin-auto-import` | artalk-sidebar | ^0.18.3 | ^21.0.0 |
|
||||
| `unplugin-vue-components` | artalk-sidebar | ^0.27.4 | ^32.0.0 |
|
||||
| `react` / `react-dom` | docs/landing | ^18.3.1 | ^19.2.5 |
|
||||
| `@vitejs/plugin-react-swc` | docs/landing | ^3.7.1 | ^4.3.0 |
|
||||
| `react-i18next` | docs/landing | ^15.0.3 | ^17.0.6 |
|
||||
| `i18next` | docs/landing | ^23.16.0 | ^26.0.8 |
|
||||
| `katex` | plugin-katex | ^0.16.11 | ^0.16.45 |
|
||||
| `lightgallery` | plugin-lightbox | ^2.7.2 | ^2.9.0 |
|
||||
| `vitepress` | docs/docs | 1.4.1 | 1.6.4 |
|
||||
| `@redocly/cli` | docs/swagger | 1.25.7 | 2.30.3 |
|
||||
| `solid-js` | plugin-auth | ^1.9.2 | ^1.9.12 |
|
||||
|
||||
---
|
||||
|
||||
## 二、包管理器与运行时要求更新
|
||||
|
||||
**`package.json` 根配置:**
|
||||
|
||||
```jsonc
|
||||
// 旧
|
||||
"packageManager": "pnpm@9.10.0"
|
||||
|
||||
// 新
|
||||
"packageManager": "pnpm@10.33.2",
|
||||
"engines": {
|
||||
"node": ">=22.19.0",
|
||||
"pnpm": ">=10.33.2"
|
||||
}
|
||||
```
|
||||
|
||||
新增 `pnpm.peerDependencyRules.allowedVersions` 以抑制 peer dep 版本警告:
|
||||
|
||||
```json
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
"allowedVersions": {
|
||||
"eslint-plugin-import>eslint": "10",
|
||||
"eslint-plugin-react>eslint": "10",
|
||||
"artalk>marked": "18"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
新增 `prepare` 脚本,`pnpm install` 后自动构建本地 workspace 包:
|
||||
|
||||
```json
|
||||
"prepare": "pnpm build:plugin-kit && pnpm build:eslint-plugin"
|
||||
```
|
||||
|
||||
本地 workspace 引用方式也由 npm 发布版改为 workspace 直引:
|
||||
|
||||
```jsonc
|
||||
// 旧
|
||||
"@artalk/plugin-kit": "^1.0.7",
|
||||
"eslint-plugin-artalk": "^1.0.2",
|
||||
|
||||
// 新
|
||||
"@artalk/plugin-kit": "workspace:*",
|
||||
"eslint-plugin-artalk": "workspace:*",
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、构建工具迁移:tsup → tsdown
|
||||
|
||||
`eslint-plugin-artalk` 和 `@artalk/plugin-kit` 从 `tsup`(基于 esbuild)迁移到 `tsdown`(基于 rolldown)。
|
||||
|
||||
### 迁移对比
|
||||
|
||||
| 项目 | tsup | tsdown |
|
||||
|------|------|--------|
|
||||
| 构建器内核 | esbuild | rolldown |
|
||||
| `?raw` 文件插件 | `esbuild-plugin-raw`,API:`setup/onResolve/onLoad` | 内联 rolldown 插件,API:`resolveId/load` |
|
||||
| externals | `external: [...]` | `deps.neverBundle: [...]` |
|
||||
| 产物扩展名 | `.js` / `.d.ts` | `.mjs`/`.cjs` / `.d.mts`/`.d.cts` |
|
||||
| 插件类型 | `esbuild.Plugin` | `TsdownPlugin`(从 `tsdown` 直接导入) |
|
||||
|
||||
### 产物入口更新
|
||||
|
||||
由于产物扩展名变化,`package.json` 的 `main`/`types`/`exports` 字段同步更新:
|
||||
|
||||
```jsonc
|
||||
// 旧
|
||||
"main": "dist/main.js",
|
||||
"types": "dist/main.d.ts",
|
||||
"exports": {
|
||||
"require": { "types": "./dist/main.d.cjs" },
|
||||
"default": { "types": "./dist/main.d.ts", "default": "./dist/main.js" }
|
||||
}
|
||||
|
||||
// 新
|
||||
"main": "dist/main.cjs",
|
||||
"types": "dist/main.d.cts",
|
||||
"exports": {
|
||||
"require": { "types": "./dist/main.d.cts", "default": "./dist/main.cjs" },
|
||||
"default": { "types": "./dist/main.d.mts", "default": "./dist/main.mjs" }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、eslint-plugin-artalk 重构
|
||||
|
||||
### 问题背景
|
||||
|
||||
`eslint@10` 内置 `@eslint/core@1.2.1`,而 `typescript-eslint@8` 依赖 `@eslint/core@0.7.0`,两者的 `RuleContext` API 不兼容,导致 `TSESLint.RuleModule` 无法直接赋值给 `eslint` 的 `Rule.RuleModule`。
|
||||
|
||||
### 解决方案
|
||||
|
||||
完全弃用 `ESLintUtils.RuleCreator`,改为直接使用 `eslint` 原生的 `Rule.RuleModule` 类型构造规则,无需类型桥接或强制转换。
|
||||
|
||||
**`src/helper.ts`(重写):**
|
||||
|
||||
```ts
|
||||
import type { Rule } from 'eslint'
|
||||
|
||||
export function createRule(def: {
|
||||
name: string
|
||||
meta: Rule.RuleMetaData
|
||||
defaultOptions?: unknown[]
|
||||
create: Rule.RuleModule['create']
|
||||
}): Rule.RuleModule {
|
||||
return {
|
||||
meta: { ...def.meta, docs: { ...def.meta.docs, url: `${docsBaseUrl}${def.name}` } },
|
||||
create: def.create,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**`src/main.ts`(重写):**
|
||||
|
||||
改为 default-only 导出,避免 rolldown MIXED_EXPORTS 警告:
|
||||
|
||||
```ts
|
||||
import type { ESLint } from 'eslint'
|
||||
|
||||
const plugin: ESLint.Plugin & { configs: typeof configs } = {
|
||||
rules: { 'artalk-plugin': artalkPlugin },
|
||||
configs,
|
||||
}
|
||||
|
||||
export default plugin
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、TypeScript 编译配置重构
|
||||
|
||||
### `tsconfig.base.json`
|
||||
|
||||
- 新增 `"composite": true`:支持项目引用(Project References),使 ESLint `projectService` 能正确解析各子包
|
||||
- 移除 `"noEmit": true`(与 `composite` 不兼容)
|
||||
- 移除 `"experimentalDecorators": true`(TypeScript 5+ 已默认支持)
|
||||
- 移除 `"allowSyntheticDefaultImports": true`(`esModuleInterop: true` 已隐含)
|
||||
|
||||
### 新增根级 `tsconfig.json`
|
||||
|
||||
新增仅供工具链(ESLint、IDE)使用的根 tsconfig,通过 `references` 聚合所有子包:
|
||||
|
||||
```json
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": { "noEmit": true, "allowJs": true },
|
||||
"files": ["eslint.config.mjs", "vitest.workspace.ts", ...],
|
||||
"references": [
|
||||
{ "path": "./ui/artalk" },
|
||||
{ "path": "./ui/artalk-sidebar" },
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 各子包 `tsconfig.json` 变更
|
||||
|
||||
需要 composite 支持但由 vite 构建(不 emit)的包(`artalk-sidebar`、`docs/landing`):
|
||||
|
||||
```jsonc
|
||||
// 不能用 noEmit,改为
|
||||
"emitDeclarationOnly": true,
|
||||
"declarationDir": ".tsbuildinfo-dts"
|
||||
```
|
||||
|
||||
并将 `.tsbuildinfo-dts/` 加入 `.gitignore`。
|
||||
|
||||
`ui/artalk/tsconfig.json` 新增:
|
||||
|
||||
```json
|
||||
"include": ["./src", "./types", "./tests", "./scripts", "package.json"],
|
||||
"files": ["./package.json"]
|
||||
```
|
||||
|
||||
原因:`composite: true` 下所有 import 的文件必须被 tsconfig 显式涵盖;`import { version } from '~/package.json'` 的 JSON 文件不会被目录 glob 匹配,需手动列出。同时 `vite-plugin-dts` 检查 `include` 而 `tsc` 检查 `files`,因此两处均需声明。
|
||||
|
||||
`ui/artalk/vite.config.ts` 为 `vite-plugin-dts` 添加:
|
||||
|
||||
```ts
|
||||
compilerOptions: { composite: false }
|
||||
```
|
||||
|
||||
避免 dts 插件在生成声明文件时触发 composite 文件列表检查(TS6307)。
|
||||
|
||||
---
|
||||
|
||||
## 六、ESLint 配置重构(`eslint.config.mjs`)
|
||||
|
||||
| 变更 | 旧 | 新 |
|
||||
|------|----|----|
|
||||
| 配置入口 | `eslintTs.config()` | `defineConfig()` from `'eslint/config'` |
|
||||
| typescript-eslint 配置展开 | `...eslintTs.configs.recommended` | `eslintTs.configs.recommended`(无需展开) |
|
||||
| vue 配置展开 | `...pluginVue.configs['flat/recommended']` | `pluginVue.configs['flat/recommended']` |
|
||||
| `@typescript-eslint` 插件 | 手动注册 `pluginTS` | 由 `typescript-eslint` 内部管理,移除手动注册 |
|
||||
| TypeScript 项目解析 | `project: tsProjects`(数组 glob) | `projectService: true`(语言服务模式) |
|
||||
| import-x resolver | `project: tsProjects` | `project: './tsconfig.json'`(单一根 tsconfig) |
|
||||
| React 版本 | `version: '18.3'` | `version: '19.2'` |
|
||||
| typed linting | `recommended` | `recommendedTypeChecked` |
|
||||
|
||||
---
|
||||
|
||||
## 七、依赖分类修复
|
||||
|
||||
### `ui/plugin-katex`
|
||||
|
||||
- `katex`:从 `dependencies` 移至 `devDependencies`(vite 配置中已 external,不打包进产物,应由用户提供)
|
||||
- `@types/katex`:从 `dependencies` + `peerDependencies` 完全移除(katex >= 0.16 已自带 `types/katex.d.ts`,`@types/katex` 为旧版遗留包)
|
||||
|
||||
### `ui/eslint-plugin-artalk`
|
||||
|
||||
- 新增 `eslint: "^10.3.0"` 到 `devDependencies`(peerDep 已声明但开发依赖缺失)
|
||||
|
||||
### `ui/plugin-kit`
|
||||
|
||||
- 新增 `vite: "^8.0.10"` 到 `devDependencies`(源码中 `import type { ViteDevServer, Plugin } from 'vite'`,peerDep 已声明但开发依赖缺失)
|
||||
|
||||
### `ui/plugin-auth`、`ui/plugin-katex`、`ui/plugin-lightbox`
|
||||
|
||||
- 新增 `"@artalk/plugin-kit": "workspace:^"` 到各自 `devDependencies`(这些插件基于 plugin-kit 开发,之前遗漏了显式声明)
|
||||
|
||||
### 根 `package.json`
|
||||
|
||||
- `@microsoft/api-extractor` 保留在根级(由 `vite-plugin-dts` 的 `bundleTypes: true` 调用,属于根级工具链依赖)
|
||||
|
||||
---
|
||||
|
||||
## 八、vitest 配置迁移
|
||||
|
||||
`vitest.workspace.ts` 从 `defineWorkspace` API 迁移到 `defineConfig` + `test.projects`(vitest v4 新 API):
|
||||
|
||||
```ts
|
||||
// 旧
|
||||
import { defineWorkspace } from 'vitest/config'
|
||||
export default defineWorkspace(['./ui/artalk/vitest.config.ts'])
|
||||
|
||||
// 新
|
||||
import { defineConfig } from 'vitest/config'
|
||||
export default defineConfig({
|
||||
test: { projects: ['./ui/artalk/vitest.config.ts'] },
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、GitHub Actions CI 更新
|
||||
|
||||
所有使用 Node.js 和 pnpm 的工作流同步更新版本:
|
||||
|
||||
| 文件 | Node.js | pnpm |
|
||||
|------|---------|------|
|
||||
| `test-frontend.yml` | `[18, 20]` → `[22]` | `9.10.0` → `10.33.2` |
|
||||
| `build-ui.yml` | `20.x` → `22.x` | `9.10.0` → `10.33.2` |
|
||||
| `test-docs.yml` | `20.x` → `22.x` | `9.10.0` → `10.33.2` |
|
||||
| `docs-cn.yml` | `20` → `22` | `9.10.0` → `10.33.2` |
|
||||
| `build-nightly.yml` | `20.x` → `22.x` | `9.10.0` → `10.33.2` |
|
||||
| `release.yml` | `20.x` → `22.x` | — |
|
||||
|
||||
---
|
||||
|
||||
## 十、文档更新
|
||||
|
||||
- `CONTRIBUTING.md`(英文):Node.js 要求 `>=20.17.0` → `>=22.19.0`,pnpm `>=9.10.0` → `>=10.33.2`,修复 `make build-fronted` 拼写错误为 `make build-frontend`
|
||||
- `docs/docs/zh/develop/contributing.md`(中文):同步上述版本要求更新
|
||||
- `docs/swagger/redocly.yaml`:更新 Redocly 配置适配 CLI v2 的新格式
|
||||
- `docs/swagger/package.json`:`swagger:serve` 命令 `npx @redocly/cli preview-docs` → `redocly preview`
|
||||
|
||||
---
|
||||
|
||||
## 十一、Go 后端依赖更新
|
||||
|
||||
`go.mod` / `go.sum` 同步更新了若干后端 Go 模块依赖(具体版本见 `go.mod` diff)。
|
||||
@@ -283,6 +283,16 @@ timezone: Asia/Shanghai
|
||||
|
||||
该值填写你所在地时区,对应 IANA 数据库时区名,参考:[Wikipedia](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) / [RFC-6557](https://www.rfc-editor.org/rfc/rfc6557.html)。
|
||||
|
||||
`timezone` 设置的是 Artalk 应用使用的时区,可以与宿主系统或容器的 `TZ` 时区不同。也可以使用环境变量 `ATK_TIMEZONE` 设置相同配置:
|
||||
|
||||
```bash
|
||||
ATK_TIMEZONE=Asia/Shanghai artalk server
|
||||
```
|
||||
|
||||
::: warning 修改后需要重启
|
||||
应用时区是进程级固定值,只在 Artalk 进程初始化时设置。运行期间不支持动态切换;修改配置文件、环境变量或控制中心中的时区后,必须完整重启 Artalk 进程或容器才能生效。
|
||||
:::
|
||||
|
||||
```
|
||||
UTC+08:00 Asia/Shanghai
|
||||
UTC+09:00 Asia/Tokyo
|
||||
|
||||
@@ -109,6 +109,7 @@ moderator:
|
||||
- **pending**:当成功匹配时,是否将评论设为待审核状态。
|
||||
- **files**:词库文件。允许多个文件,Artalk 启动时会合并词库。
|
||||
- **file_sep**:词库文件内容分割符。例如:文件中每行一个词语,该项配置 `\n`。
|
||||
该配置不能为空。控制中心会将换行、回车和制表符分别显示为 `\n`、`\r`、`\t`;若分隔符需要是字面量 `\n`,请输入 `\\n`。
|
||||
- **replace_to**:替换字符。例如:该项设置为 `x`,你可以将 `pending` 设置为 `false`,评论自动过审,但匹配到的词语会被替换为 `x`,例如 `fxxk`、`xxxx`。
|
||||
|
||||
注:`replace_to` 不建议使用 `*` 星号,应为它和 Markdown 的加粗语法冲突。
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
## 更新变动
|
||||
|
||||
突破性变动,例如更新需要修改原有配置等,请注意查看版本更新日志中的提示:[CHANGELOG.md](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md)
|
||||
突破性变动,例如更新需要修改原有配置等,请注意查看版本更新日志中的提示:
|
||||
|
||||
- [v2.10.0 变更日志与迁移指南](../releases/v2.10.0.md)
|
||||
- [完整 CHANGELOG.md](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md)
|
||||
|
||||
## 命令行一键升级
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
- [青空之蓝](https://blog.ixk.me/)
|
||||
- [Monstx](https://blog.monsterx.cn/)
|
||||
- [QWQAQ](https://qwqaq.com/)
|
||||
- [xaoxuu](https://xaoxuu.com)
|
||||
|
||||
::: tip
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ services:
|
||||
- ./data:/data
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
- ATK_TIMEZONE=Asia/Shanghai
|
||||
- ATK_LOCALE=zh-CN
|
||||
- ATK_SITE_DEFAULT=Artalk 的博客
|
||||
- ATK_SITE_URL=https://example.com
|
||||
@@ -36,6 +37,8 @@ services:
|
||||
- ATK_ADMIN_USERS_0_BADGE_COLOR=#0083FF
|
||||
```
|
||||
|
||||
`TZ` 设置容器或宿主进程环境的时区,`ATK_TIMEZONE` 设置 Artalk 应用时区;两者可以不同。Artalk 应用时区只在进程初始化时生效,修改 `ATK_TIMEZONE` 后必须完整重启 Artalk 进程或容器。
|
||||
|
||||
当变量为数组,通过空格分隔的字符串或数字索引来设置数组值,例如:
|
||||
|
||||
```
|
||||
@@ -165,6 +168,8 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
| **ATK_AUTH_SLACK_CLIENT_ID** | `""` | ClientId | auth.slack.client_id (社交登录 > Slack > ClientId) |
|
||||
| **ATK_AUTH_SLACK_CLIENT_SECRET** | `""` | ClientSecret | auth.slack.client_secret (社交登录 > Slack > ClientSecret) |
|
||||
| **ATK_AUTH_SLACK_ENABLED** | `false` | 启用 | auth.slack.enabled (社交登录 > Slack > Enabled) |
|
||||
| **ATK_AUTH_SSO_ENABLED** | `false` | 启用 SSO 令牌交换 | auth.sso.enabled (社交登录 > SSO > 启用 SSO 令牌交换) |
|
||||
| **ATK_AUTH_SSO_ISSUER** | `""` | OIDC issuer 地址 (如 "tenant.auth0.com" 或 "https://tenant.auth0.com") | auth.sso.issuer (社交登录 > SSO > OIDC issuer 地址) |
|
||||
| **ATK_AUTH_STEAM_API_KEY** | `""` | ApiKey | auth.steam.api_key (社交登录 > Steam > ApiKey) |
|
||||
| **ATK_AUTH_STEAM_ENABLED** | `false` | 启用 | auth.steam.enabled (社交登录 > Steam > Enabled) |
|
||||
| **ATK_AUTH_TIKTOK_CLIENT_ID** | `""` | ClientId | auth.tiktok.client_id (社交登录 > Tiktok > ClientId) |
|
||||
|
||||
@@ -69,6 +69,62 @@ Artalk 支持同时启用多种登录方式,用户可以选择任意一种方
|
||||
|
||||
接入 GitHub 登录可参考文档:[关于创建 GitHub 应用](https://docs.github.com/zh/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps),得到 Client ID 和 Client Secret 后,填写到 Artalk 控制中心的设置页面的社交登录中的「GitHub」选项中即可。
|
||||
|
||||
## SSO 令牌交换
|
||||
|
||||
如果你的站点被嵌入在一个已经通过外部 OIDC 身份提供商(Auth0、Keycloak、Okta 等)完成用户认证的应用中,可以让 Artalk 复用该登录态,而不必显示自己的登录界面。外层应用用它已经持有的 IdP 访问令牌换取一个 Artalk 会话令牌,这样已在父应用登录的用户无需额外点击或弹窗即可发表评论。
|
||||
|
||||
该功能**默认关闭**,需手动开启。可通过 [配置文件](../backend/config.md) 或 [环境变量](../env.md#社交登录) 启用,并填写你所用提供商的 OIDC issuer:
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
enabled: true
|
||||
sso:
|
||||
enabled: true
|
||||
issuer: "https://tenant.auth0.com" # 例如 "tenant.auth0.com" 或 "https://tenant.auth0.com"
|
||||
```
|
||||
|
||||
启用后,外层应用将 IdP 访问令牌提交到交换接口:
|
||||
|
||||
```
|
||||
POST /api/v2/sso/exchange
|
||||
Content-Type: application/json
|
||||
|
||||
{ "token": "<外部 IdP 访问令牌>" }
|
||||
```
|
||||
|
||||
Artalk 通过调用 issuer 的 `/userinfo` 端点校验该令牌(OIDC 标准做法——由 IdP 在服务端验证并签名响应,因此 Artalk 侧无需处理密钥),读取其中的 `email` 声明。只有同时返回 `email_verified: true` 时,Artalk 才会查找或创建对应用户,并返回与其它登录接口一致的响应结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "<Artalk 会话令牌>",
|
||||
"user": { "name": "...", "email": "...", "is_admin": false }
|
||||
}
|
||||
```
|
||||
|
||||
前端在 `Artalk.init()` 运行前将其写入 `localStorage["ArtalkUser"]`,组件即把该用户视为已完全登录(不弹窗,管理员用户也不再要求输入管理密码):
|
||||
|
||||
```js
|
||||
const res = await fetch('https://comments.example.com/api/v2/sso/exchange', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ token: idpAccessToken }), // 来自你的 IdP 的访问令牌
|
||||
})
|
||||
const { token, user } = await res.json()
|
||||
|
||||
// 在组件挂载前预填 Artalk 登录态
|
||||
localStorage.setItem('ArtalkUser', JSON.stringify({ ...user, token }))
|
||||
|
||||
Artalk.init({ el: '#Comments', server: 'https://comments.example.com', site: 'Blog' })
|
||||
```
|
||||
|
||||
当 SSO 未启用时接口返回 `404`,IdP 拒绝令牌或邮箱未验证时返回 `401`,令牌不含邮箱声明时返回 `400`。Artalk 不提供跳过邮箱验证的开关。
|
||||
|
||||
::: tip
|
||||
|
||||
校验完全依赖 issuer 的 `/userinfo` 拒绝无效或已吊销的令牌,且用户按 `email` 声明匹配。请仅对你信任的 issuer 启用此功能。未经验证的邮箱可能冒充现有用户甚至管理员,因此 Artalk 强制要求 `email_verified: true`。
|
||||
|
||||
:::
|
||||
|
||||
## 插件开发
|
||||
|
||||
Artalk 的社交登录功能是通过独立的插件实现并采用 Solid.js 开发,代码可在 [@ArtalkJS/Artalk:ui/plugin-auth](https://github.com/ArtalkJS/Artalk/tree/master/ui/plugin-auth) 找到。
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
# Artalk v2.10.0 变更日志与迁移指南
|
||||
|
||||
v2.10.0 是 v2.9.1 之后的下一个正式版本。本版本新增页面投票、SSO Token Exchange、MySQL Cloud SQL TLS 和无 CGO 的 SQLite 构建,并重构了 UI 客户端的生命周期、配置与类型系统。
|
||||
|
||||
::: warning 升级前必读
|
||||
v2.10.0 包含有意保留的接口与行为变更。服务端与 UI 客户端应同步升级;主题、插件、直接调用 HTTP API 或引用 Artalk TypeScript 类型的项目应先按本文完成迁移。
|
||||
:::
|
||||
|
||||
## 升级检查清单
|
||||
|
||||
- 备份数据库、配置文件和自定义前端资源。
|
||||
- 二进制部署确认工作目录和所有相对文件路径;需要保持旧行为时显式传入 `-w` 和绝对 `-c` 路径。
|
||||
- 自行编译或打包服务端时升级到 Go 1.26.5。
|
||||
- 如需修改应用时区,设置 `timezone` 或 `ATK_TIMEZONE` 后完整重启 Artalk。
|
||||
- 同时升级服务端与 UI 客户端,避免新旧 Vote API 混用。
|
||||
- 检查本地与远程界面配置的优先级;默认改为本地配置优先。
|
||||
- 搜索 `useBackendConf`、`remoteConfModifier`、两参数 `ctx.inject()`、直接赋值 `ctx.conf` / `ctx.$root` 和 `artalk.update(...).xxx()`。
|
||||
- 重新编译使用 Artalk 类型或内部 UI 服务的主题与插件。
|
||||
- Node.js 工具链使用现代版本;Artalk 仓库的开发基线为 Node.js 22.19 及以上。
|
||||
|
||||
## 主要更新
|
||||
|
||||
### UI 与交互
|
||||
|
||||
- 新增[页面投票](../frontend/voting.md#页面投票),并可显示当前访客的已投票状态([#983](https://github.com/ArtalkJS/Artalk/issues/983)、[#998](https://github.com/ArtalkJS/Artalk/issues/998))。
|
||||
- 新增 `preferRemoteConf`,明确本地与远程界面配置的合并优先级([#1000](https://github.com/ArtalkJS/Artalk/issues/1000)、[#1011](https://github.com/ArtalkJS/Artalk/issues/1011))。
|
||||
- UI 改用依赖注入容器,并重新整理模块、生命周期和公开类型([#1006](https://github.com/ArtalkJS/Artalk/issues/1006)、[#1007](https://github.com/ArtalkJS/Artalk/issues/1007))。
|
||||
- 新增 Artalk UI ESLint 插件,并升级到 v1.0.2([#995](https://github.com/ArtalkJS/Artalk/issues/995)、[#1010](https://github.com/ArtalkJS/Artalk/issues/1010))。
|
||||
- User-Agent 识别新增 OpenHarmony 和更多浏览器支持([#1087](https://github.com/ArtalkJS/Artalk/issues/1087))。
|
||||
- 评论链接的 `rel` 属性新增 `ugc`([#1120](https://github.com/ArtalkJS/Artalk/issues/1120))。
|
||||
|
||||
### 服务端与 API
|
||||
|
||||
- 新增 `POST /api/v2/sso/exchange`,可将受信任 OIDC 身份提供方的访问令牌交换为 Artalk 会话令牌([#1134](https://github.com/ArtalkJS/Artalk/issues/1134))。该功能默认关闭;生产环境应只配置受信任的 HTTPS Issuer,且 `/userinfo` 必须返回 `email_verified: true`。
|
||||
- Vote API 现在统一使用目标类型、目标 ID 和选择,并新增投票状态查询接口。
|
||||
- 改进配置文件与数据目录查找,保留切换数据目录前的配置文件定位结果([#992](https://github.com/ArtalkJS/Artalk/issues/992))。
|
||||
- MySQL 新增 Google Cloud SQL 双向 TLS 配置支持([#1104](https://github.com/ArtalkJS/Artalk/issues/1104))。
|
||||
|
||||
### 构建与运行环境
|
||||
|
||||
- SQLite 改用纯 Go 驱动,正式构建不再依赖 CGO,也不再需要单独的 `GORELEASER_CROSS` 工具链。跨平台产物可由标准 Go 工具链直接构建。
|
||||
- Go 工具链升级到 v1.26.5,并更新相关依赖;登录会话的 JWT 实现升级到受维护的 v5 版本。已有 HS256 Token 的声明与签名格式保持兼容,升级服务端不会因此强制用户重新登录。
|
||||
- 官方容器的构建与运行基础镜像升级到 Alpine 3.24;Go 构建版本仍固定为 v1.26.5,容器内的数据目录和配置路径保持不变。
|
||||
- 更新前端与文档工具链,包括 Vite 8.1、Marked 18.0.7 和 KaTeX 0.17。`@artalk/plugin-katex` 的 peer 范围仍兼容 KaTeX 0.16.45 至 0.17.x,依赖升级不会强制现有主题同步更换 KaTeX。
|
||||
- 新增插件注册表拉取脚本和 npm 已发布版本检查脚本。
|
||||
- npm 包使用现代条件导出;不再兼容 Node.js 10 的旧模块解析行为。
|
||||
|
||||
### 文档与本地化
|
||||
|
||||
- 新增土耳其语(`tr-TR`)和繁体中文配置模板。
|
||||
- 新增日语 README。
|
||||
- 重设计项目首页,补充多语言内容并优化响应式字号。
|
||||
- 文档站集成 Git Changelog。
|
||||
|
||||
## 安全性与可靠性
|
||||
|
||||
- 后端 JWT 解析器升级到 `github.com/golang-jwt/jwt/v5`,显式限制为 HS256,继续校验过期时间,并新增旧 Token 格式、签名算法和过期行为的回归测试。
|
||||
- Markdown HTML 清理器由无修复版本且存在 ReDoS 风险的 `insane` 切换为持续维护的 `DOMPurify`,保留原有标签、属性、class、style 和 URL scheme 白名单,并增加恶意标记与 ReDoS 回归测试。
|
||||
- 前端锁文件将构建与开发工具链中的 `fast-uri`、`brace-expansion`、`js-yaml`、`undici`、Vite 等传递依赖固定到已修复版本;完整工作区依赖审计未发现已知漏洞。
|
||||
- Google Cloud SQL TLS 会同时加载服务端 CA、客户端证书与私钥,校验服务器证书和主机名,并要求 TLS 1.2 及以上。三个证书路径必须一起配置,避免部分配置被静默忽略。
|
||||
- Vote API 会拒绝非法、非正数、不存在或类型未知的投票目标。投票记录和目标计数在同一数据库事务中更新,任一步骤失败都会回滚。
|
||||
- 插件 URL 由服务端过滤:相对 URL 与受信任 URL 可下发给客户端,不受信任的绝对 URL 会被移除。
|
||||
- 插件注册表下载仅允许 HTTPS 和限定主机,并限制重定向次数、响应大小与请求时间。
|
||||
- npm 版本检查不再通过 Shell 拼接包名,并会把网络或解析错误作为失败返回。
|
||||
- 进程时区只在初始化时设置,避免并发请求修改全局时区状态。
|
||||
|
||||
## 突破性变更与迁移
|
||||
|
||||
### 1. 本地界面配置默认优先
|
||||
|
||||
v2.9.x 中远程配置会覆盖 `Artalk.init()` 传入的本地配置。v2.10.0 默认改为本地配置优先:
|
||||
|
||||
```text
|
||||
Artalk.init() 本地配置 > 环境变量 > 控制中心 = 配置文件
|
||||
```
|
||||
|
||||
默认无需新增配置:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
server: 'https://comments.example.com',
|
||||
site: 'My Site',
|
||||
locale: 'zh-CN', // 保留本地值
|
||||
})
|
||||
```
|
||||
|
||||
如需恢复“远程配置优先”,显式开启:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
server: 'https://comments.example.com',
|
||||
site: 'My Site',
|
||||
preferRemoteConf: true,
|
||||
})
|
||||
```
|
||||
|
||||
`useBackendConf` 已废弃并始终按 `true` 处理。设置为 `false` 不再阻止客户端读取后端必须提供的配置;它不能替代 `preferRemoteConf`。
|
||||
|
||||
### 2. 删除 `remoteConfModifier`
|
||||
|
||||
`remoteConfModifier` 不再属于公开配置。请根据用途迁移:
|
||||
|
||||
- 固定覆盖项直接放入 `Artalk.init()`,默认会优先于远程配置。
|
||||
- 需要控制中心优先时使用 `preferRemoteConf: true`。
|
||||
- 需要控制首次评论请求时使用 `fetchCommentsOnInit`。
|
||||
- 仅需初始化后更新时,监听 `created` 后调用 `update()`。
|
||||
|
||||
```js
|
||||
const artalk = Artalk.init({
|
||||
server: 'https://comments.example.com',
|
||||
site: 'My Site',
|
||||
fetchCommentsOnInit: false,
|
||||
})
|
||||
|
||||
artalk.on('created', () => {
|
||||
artalk.update({ locale: 'zh-CN' })
|
||||
artalk.reload()
|
||||
})
|
||||
```
|
||||
|
||||
不再提供可在插件初始化前任意修改远程配置的等价 Hook。
|
||||
|
||||
### 3. 配置类型区分完整值与输入值
|
||||
|
||||
`Config` 表示已经解析完成的完整配置,`ConfigPartial` 表示 `Artalk.init()`、`update()` 和插件配置更新所接受的递归可选输入。
|
||||
|
||||
```ts
|
||||
import type { Config, ConfigPartial } from 'artalk'
|
||||
|
||||
function createComments(conf: ConfigPartial) {
|
||||
return Artalk.init(conf)
|
||||
}
|
||||
|
||||
function readResolvedConfig(conf: Config) {
|
||||
console.log(conf.server, conf.pagination.pageSize)
|
||||
}
|
||||
```
|
||||
|
||||
过去将 `Partial<ArtalkConfig>` 用作初始化参数的代码应改用 `ConfigPartial`。`ArtalkConfig` 仍可导入,但现在表示完整配置。
|
||||
|
||||
`ConfigPartial` 会保持函数、DOM 元素、`Date`、`RegExp` 和数组元素的原始语义,不会再把这些值错误地递归拆成对象字段。
|
||||
|
||||
### 4. DI 注册与解析分离
|
||||
|
||||
旧的双参数 `ctx.inject(key, value)` 注册方式已移除。使用 `provide()` 注册,使用 `inject()` 解析:
|
||||
|
||||
```ts
|
||||
// v2.9.x
|
||||
ctx.inject('example', createExample())
|
||||
const example = ctx.get('example')
|
||||
|
||||
// v2.10.0
|
||||
ctx.provide('example', () => createExample())
|
||||
const example = ctx.inject('example')
|
||||
```
|
||||
|
||||
`provide()` 支持依赖列表与 `singleton` / `transient` 生命周期。自定义 TypeScript 服务键应通过声明合并扩展 `Services`。
|
||||
|
||||
### 5. `ctx.conf` 与 `ctx.$root` 不可替换
|
||||
|
||||
两者仍可读取,但不能整体赋值。运行时直接赋值会抛出带迁移提示的错误:
|
||||
|
||||
```ts
|
||||
// 错误
|
||||
ctx.conf = nextConf
|
||||
ctx.$root = nextElement
|
||||
|
||||
// 正确
|
||||
ctx.updateConf(nextConf)
|
||||
const conf = ctx.getConf()
|
||||
const root = ctx.getEl()
|
||||
```
|
||||
|
||||
如需更换根元素,请销毁旧实例并使用新的 `el` 创建实例。
|
||||
|
||||
### 6. `Artalk.update()` 返回 `void`
|
||||
|
||||
`update()` 不再返回 Artalk 实例。将链式调用拆开:
|
||||
|
||||
```js
|
||||
// v2.9.x
|
||||
artalk.update({ pageKey: '/next' }).reload()
|
||||
|
||||
// v2.10.0
|
||||
artalk.update({ pageKey: '/next' })
|
||||
artalk.reload()
|
||||
```
|
||||
|
||||
### 7. 插件初始化与 `created` 时点
|
||||
|
||||
内置插件、本地插件和网络插件现在都会在本地与远程配置完成合并后初始化。网络插件不会再看到仅来自某一端的临时配置。
|
||||
|
||||
`created` 会在所有插件完成初始化后触发,随后触发 `mounted`。依赖旧时序的插件应把初始化前置逻辑放在插件函数内,把需要完整插件环境的逻辑放到 `created` 监听器中。
|
||||
|
||||
### 8. Vote HTTP API 与客户端方法
|
||||
|
||||
旧的投票端点:
|
||||
|
||||
```http
|
||||
POST /api/v2/votes/comment_up/123
|
||||
```
|
||||
|
||||
新端点:
|
||||
|
||||
```http
|
||||
GET /api/v2/votes/comment/123
|
||||
POST /api/v2/votes/comment/123/up
|
||||
```
|
||||
|
||||
`target_name` 只能为 `comment` 或 `page`,`choice` 只能为 `up` 或 `down`。
|
||||
|
||||
生成的客户端方法同步调整:
|
||||
|
||||
```ts
|
||||
await api.votes.getVote('comment', 123)
|
||||
await api.votes.createVote('comment', 123, 'up', { name, email })
|
||||
```
|
||||
|
||||
旧端点和旧 `votes.vote()` 方法不保留兼容适配器。
|
||||
|
||||
### 9. SQLite 驱动改为纯 Go 实现
|
||||
|
||||
服务端 SQLite 驱动由基于 `mattn/go-sqlite3` 的 CGO 实现切换为 `github.com/libtnb/sqlite` 提供的纯 Go 实现。正式构建不再依赖本地 C 编译器、`CGO_ENABLED=1` 或 `GORELEASER_CROSS_VERSION` 对应的交叉编译工具链。
|
||||
|
||||
SQLite 磁盘文件格式没有改变,现有 `db.file` 数据库可以直接使用,普通部署无需修改数据库配置。升级前仍建议备份数据库;首次使用新版本启动时,Artalk 会照常执行数据库结构迁移。
|
||||
|
||||
自行编译或将 Artalk 作为 Go 模块嵌入的项目需要注意:
|
||||
|
||||
- 可以删除 SQLite 构建所需的 C 编译器、CGO 和 Goreleaser Cross 配置。
|
||||
- 不要再依赖 `mattn/go-sqlite3` 专属的构建标签、DSN 参数、C API 或动态扩展加载行为。
|
||||
- 如有自定义 SQLite 编译选项、扩展或原生备份工具集成,应使用 v2.10.0 重新执行迁移、并发读写和备份恢复测试。
|
||||
|
||||
此变更不影响使用 MySQL、PostgreSQL、MSSQL 的部署。
|
||||
|
||||
### 10. 默认配置和数据目录采用 XDG 规则
|
||||
|
||||
未指定 `--workdir` / `-w` 时,Artalk 不再始终把启动目录作为工作目录。v2.10.0 的默认查找顺序如下:
|
||||
|
||||
- 配置文件:启动目录、启动目录下的 `data`、`$XDG_CONFIG_HOME/artalk`、`/etc/artalk`。
|
||||
- 数据目录:如果启动目录存在 `./data` 则保持旧工作目录;否则依次使用 `$XDG_DATA_HOME/artalk`、`/var/lib/artalk`,均不存在时创建 `$XDG_DATA_HOME/artalk`。
|
||||
|
||||
配置文件会在隐式切换数据目录前完成定位,但配置中的数据库、日志、上传目录等相对路径,以及 `import`、`export`、`gen` 等命令的相对文件参数,仍然基于最终工作目录解析。
|
||||
|
||||
现有目录中已经包含 `./data` 的常规部署保持原行为。官方 Docker 镜像也显式指定了工作目录和配置文件,不受此变化影响。
|
||||
|
||||
如果二进制部署需要完全保持旧路径语义,请显式指定工作目录和配置文件:
|
||||
|
||||
```bash
|
||||
artalk -w /srv/artalk -c /srv/artalk/artalk.yml server
|
||||
```
|
||||
|
||||
升级前应检查 `db.file`、`log.filename`、`img_upload.path`、IP 数据库和关键词文件等所有相对路径,避免在新的数据目录中意外创建空数据库或文件。
|
||||
|
||||
### 11. 源码构建要求 Go 1.26.5
|
||||
|
||||
`go.mod` 的最低 Go 版本从 1.22.7 提升到 1.26.5。该变化影响 `go install`、自行编译、发行版打包和将 Artalk 纳入 Go 构建流程的下游项目。
|
||||
|
||||
正式二进制和 Docker 镜像用户无需安装 Go。源码构建环境应升级到 Go 1.26.5;禁用 Go Toolchain 自动下载或处于离线环境的 CI 必须提前安装对应工具链。
|
||||
|
||||
### 12. 应用时区在进程初始化时固定
|
||||
|
||||
`timezone` 配置和 `ATK_TIMEZONE` 环境变量继续保留,用于设置独立于宿主系统或容器 `TZ` 的 Artalk 应用时区:
|
||||
|
||||
```yaml
|
||||
timezone: Asia/Shanghai
|
||||
```
|
||||
|
||||
```bash
|
||||
ATK_TIMEZONE=Asia/Shanghai artalk server
|
||||
```
|
||||
|
||||
时区属于进程级全局状态,v2.10.0 只在 Artalk 进程初始化时设置一次,运行期间不支持动态切换。修改配置文件、环境变量或控制中心中的时区后,必须完整重启 Artalk 进程或容器;仅调用控制中心的“应用配置”不足以切换时区。
|
||||
|
||||
### 13. `marked` 18 与现代包入口
|
||||
|
||||
默认 Artalk 构建继续内置 Markdown 能力;ArtalkLite 不把 `marked` 打包进自身,需要下游提供兼容的 `marked@^18.0.3`。
|
||||
|
||||
Artalk 使用的 `Marked`、`Renderer`、`parse()`、`setOptions()`、`use()` 和 Tokenizer Extension 均不是 v18 专属 API,但 v14 到 v18 的上游解析细节仍可能影响自定义 Renderer、Tokenizer 或快照输出。使用这些扩展的项目应重新运行 Markdown 测试。
|
||||
|
||||
Node.js 10 和依赖旧式包解析的工具链不再受支持。Artalk 仓库开发与构建使用 Node.js 22.19 及以上。
|
||||
|
||||
## 保留的兼容入口
|
||||
|
||||
为了给主题和第三方插件留出迁移时间,以下入口在 v2.10.0 仍然存在,但已经标记为 `deprecated`:
|
||||
|
||||
| 旧入口 | 推荐入口 |
|
||||
| --- | --- |
|
||||
| `ContextApi` | `Context` |
|
||||
| `DataManagerApi` | `DataManager` |
|
||||
| `EditorApi` | `Editor` |
|
||||
| `ctx.data` | `ctx.getData()` 或 `ctx.inject('data')` |
|
||||
| `ctx.user` | `ctx.getUser()` 或 `ctx.inject('user')` |
|
||||
| `ctx.editor` | `ctx.inject('editor')` |
|
||||
| `ctx.list` | `ctx.inject('list')` |
|
||||
| `ctx.layerManager` | `ctx.inject('layers')` |
|
||||
| `ctx.checkerLauncher` | `ctx.inject('checkers')` |
|
||||
| `ctx.sidebarLayer` | `ctx.inject('sidebar')` |
|
||||
| `ctx.editorPlugs` | `ctx.inject('editorPlugs')` |
|
||||
| `editor.$el` | `editor.getEl()` |
|
||||
| `editor.getPlugs()` | `editor.getPlugins()` |
|
||||
| `editor.setReply()` | `editor.setReplyComment()` |
|
||||
|
||||
这些别名仅用于过渡;新代码应直接使用推荐入口。完整公开类型请查看 [TypeDoc](https://artalk.js.org/typedoc/)。
|
||||
|
||||
## 其他变更
|
||||
|
||||
- 评论回复按钮改用组件实现。
|
||||
- Sidebar 与 Auth UI 已适配新的客户端类型。
|
||||
- 新增 npm 发布版本检查与插件注册表生成流程。
|
||||
- 固定 Swagger TypeScript 客户端生成器版本并更新命令,避免不同开发环境通过 `npx` 获取不兼容版本。
|
||||
- 更新 GitHub Actions 运行时及制品、缓存、Docker、CodeQL 和 Codecov 相关 Actions;工作流输入与发布产物布局保持不变。
|
||||
- 更新前端、Go 和文档依赖,并清理旧测试项目与 CI 配置。
|
||||
|
||||
完整提交差异可查看 [`v2.9.1...v2.10.0`](https://github.com/ArtalkJS/Artalk/compare/v2.9.1...v2.10.0)。
|
||||
@@ -138,15 +138,19 @@ typecho-20220424-202246.artrans
|
||||
|
||||
### Commento
|
||||
|
||||
你可在 Commento 后台导出 JSON 格式的数据文件,然后使用[转换工具](#转换工具)进行转换。
|
||||
前往 Commento 后台,依次点击「General - Export」,导出 JSON 格式的数据文件,然后使用[转换工具](#转换工具)进行转换。
|
||||
|
||||
【图示,待补充...】
|
||||

|
||||
|
||||
### Twikoo
|
||||
|
||||
[Twikoo](https://twikoo.js.org/) 是一款基于腾讯云开发的评论系统,可前往 [腾讯云后台](https://console.cloud.tencent.com/tcb) 导出 JSON 格式的评论数据,然后使用[转换工具](#转换工具)进行转换。
|
||||
[Twikoo](https://twikoo.js.org/) 可以通过所在管理面板中的「导出」板块获取JSON 文件,然后使用[转换工具](#转换工具)进行转换。
|
||||
|
||||
<img src="/images/transfer/tencent-tcb.png" style="max-width: 480px;">
|
||||

|
||||
|
||||
除此之外,对于腾讯云一键部署的Twikoo,可前往 [腾讯云后台](https://console.cloud.tencent.com/tcb) 导出 JSON 格式的评论数据。
|
||||
|
||||

|
||||
|
||||
### Artalk v1 (PHP 旧版后端)
|
||||
|
||||
|
||||
+12
-11
@@ -15,19 +15,20 @@
|
||||
"update:readme": "npx tsx ./scripts/update-readme.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@headlessui/react": "^2.1.10",
|
||||
"@types/react": "^18.3.11",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react-swc": "^3.7.1",
|
||||
"i18next": "^23.16.0",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"@headlessui/react": "^2.2.10",
|
||||
"@types/node": "25.9.5",
|
||||
"@types/react": "^19.2.17",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react-swc": "^4.3.2",
|
||||
"i18next": "^26.3.6",
|
||||
"i18next-browser-languagedetector": "^8.2.1",
|
||||
"include-media": "^2.0.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^15.0.3",
|
||||
"react-icons": "5.3.0",
|
||||
"usehooks-ts": "^3.1.0",
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8",
|
||||
"react-i18next": "^17.0.10",
|
||||
"react-icons": "5.7.0",
|
||||
"usehooks-ts": "^3.1.1",
|
||||
"vanilla-lazyload": "^19.1.3"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
export const useIsElementVisible = (target: React.RefObject<HTMLDivElement>) => {
|
||||
export const useIsElementVisible = (target: React.RefObject<HTMLDivElement | null>) => {
|
||||
const [isVisible, setIsVisible] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -13,10 +13,11 @@ export const useIsElementVisible = (target: React.RefObject<HTMLDivElement>) =>
|
||||
},
|
||||
)
|
||||
|
||||
target.current && observer.observe(target.current)
|
||||
const targetEl = target.current
|
||||
targetEl && observer.observe(targetEl)
|
||||
|
||||
return () => {
|
||||
target.current && observer.unobserve(target.current)
|
||||
targetEl && observer.unobserve(targetEl)
|
||||
}
|
||||
}, [target])
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export const en = {
|
||||
docs: 'Docs',
|
||||
changelog: 'Changelog',
|
||||
feature_slight_title: 'SLIGHT',
|
||||
feature_slight_desc_line_1: 'A native JS client, framework-agnostic,',
|
||||
feature_slight_desc_line_1: 'A native JS client, framework-agnostic, ',
|
||||
feature_slight_desc_line_2:
|
||||
'compact in size, fast to load, and instantly responsive to every interaction.',
|
||||
feature_swift_title: 'SWIFT',
|
||||
|
||||
+10
-10
@@ -4,21 +4,21 @@ export const fr: MessageSchema = {
|
||||
home_title: 'Artalk | Système de commentaires auto-hébergé alimenté par Golang',
|
||||
get_artalk: 'Obtenir Artalk',
|
||||
get_artalk_link: 'https://artalk.js.org/guide/intro.html',
|
||||
slogan_line_1: 'Auto-hébergé',
|
||||
slogan_line_2: 'Commentaire',
|
||||
slogan_line_3: 'Système',
|
||||
slogan_line_1: 'Système de',
|
||||
slogan_line_2: 'commentaires',
|
||||
slogan_line_3: 'auto-hébergé',
|
||||
nav_docs_link: 'https://artalk.js.org/guide/intro.html',
|
||||
nav_changelog_link: 'https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md',
|
||||
github_link: 'https://github.com/ArtalkJS/Artalk',
|
||||
docs: 'Documentation',
|
||||
changelog: 'Journal des modifications',
|
||||
feature_slight_title: 'Léger',
|
||||
feature_slight_desc_line_1: 'Programme frontal en JS natif, sans dépendance à un framework,',
|
||||
feature_slight_desc_line_1: 'Programme frontal en JS natif, sans dépendance à un framework, ',
|
||||
feature_slight_desc_line_2:
|
||||
'taille compacte, chargement rapide et réponse instantanée à chaque interaction.',
|
||||
feature_swift_title: 'Rapide',
|
||||
feature_swift_desc_line_1:
|
||||
'Artalk est écrit en Golang, offrant rapidité, multiplateforme et facilité de déploiement.',
|
||||
'Artalk est écrit en Golang, offrant rapidité, compatibilité multiplateforme et facilité de déploiement.',
|
||||
feature_swift_desc_line_2:
|
||||
'Nous proposons également une image Docker pour un déploiement encore plus simple.',
|
||||
feature_swift_self_compile: 'Vous souhaitez compiler le code vous-même ?',
|
||||
@@ -43,7 +43,7 @@ export const fr: MessageSchema = {
|
||||
'Artalk est un logiciel open-source, auto-hébergé et contrôlable en toute transparence.',
|
||||
feature_safe_desc_line_2:
|
||||
'Artalk inclut un outil de migration pour transférer facilement les commentaires.',
|
||||
feature_safe_desc_line_3: 'Nous priorisons la sécurité et mettons à jour régulièrement.',
|
||||
feature_safe_desc_line_3: 'Nous priorisons la sécurité et mettons le site à jour régulièrement.',
|
||||
feature_safe_report_audit_hint: 'Vous avez trouvé un problème de sécurité ?',
|
||||
feature_safe_report_audit_btn: 'Signaler maintenant',
|
||||
feature_safe_report_audit_link: 'https://github.com/ArtalkJS/Artalk/issues',
|
||||
@@ -82,7 +82,7 @@ export const fr: MessageSchema = {
|
||||
feature_sidebar_name: 'Barre latérale',
|
||||
feature_sidebar_desc: 'Gestion rapide, navigation intuitive',
|
||||
feature_social_login_name: 'Connexion via réseaux sociaux',
|
||||
feature_social_login_desc: 'Connexion rapide avec un compte réseau social',
|
||||
feature_social_login_desc: 'Connexion rapide avec un compte de réseau social',
|
||||
feature_email_notification_name: 'Notification par e-mail',
|
||||
feature_email_notification_desc: "Plusieurs méthodes d'envoi et modèles d'e-mail",
|
||||
feature_diverse_push_name: 'Notifications diverses',
|
||||
@@ -153,7 +153,7 @@ export const fr: MessageSchema = {
|
||||
intro_stack_card_3_text: 'Bonjour',
|
||||
concept_section_locales_title: 'Localisation multilingue',
|
||||
concept_section_locales_desc:
|
||||
"Prise en charge de plusieurs langues, telles que l'anglais, le chinois, le français, le japonais, le coréen et le russe.",
|
||||
"Nous prenons en charge plusieurs langues, telles que l'anglais, le chinois, le français, le japonais, le coréen et le russe.",
|
||||
concept_section_docker_title: 'Installation avec Docker',
|
||||
concept_section_docker_desc_line_1:
|
||||
'Nous fournissons une image Docker pour une installation rapide en un seul clic.',
|
||||
@@ -161,7 +161,7 @@ export const fr: MessageSchema = {
|
||||
'Construit avec Golang, il est compatible avec les déploiements multi-plateformes, vous permettant de le lancer où que vous soyez.',
|
||||
concept_section_open_source_title: 'Open Source',
|
||||
concept_section_open_source_desc_line_1:
|
||||
'Artalk est un logiciel libre et open source, axé sur la confidentialité et la sécurité des données.',
|
||||
'Artalk est un logiciel gratuit et open source, axé sur la confidentialité et la sécurité des données.',
|
||||
concept_section_open_source_desc_line_2:
|
||||
"En maintenance continue depuis {{year}} ans, nous proposons des mises à jour régulières pour améliorer l'expérience utilisateur et garantir la stabilité.",
|
||||
feature_community_title: 'Élargissez votre expérience',
|
||||
@@ -171,5 +171,5 @@ export const fr: MessageSchema = {
|
||||
any_website_or_blog: "n'importe quel site web ou blog",
|
||||
see_more_on_github: 'Voir plus sur GitHub',
|
||||
star_proposal_line_1: "Si vous aimez Artalk, n'hésitez pas à lui donner une étoile 🌟",
|
||||
star_proposal_line_2: 'Cela nous serait très utile !',
|
||||
star_proposal_line_2: 'Ça nous ferait vraiment plaisir !',
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"types": ["node", "vite/client"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
@@ -11,7 +12,9 @@
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"composite": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"declarationDir": ".tsbuildinfo-dts",
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
"moduleResolution": "bundler"
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"fileNames":["../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2025.float16.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/.pnpm/@types+react@19.2.17/node_modules/@types/react/global.d.ts","../../node_modules/.pnpm/csstype@3.2.3/node_modules/csstype/index.d.ts","../../node_modules/.pnpm/@types+react@19.2.17/node_modules/@types/react/index.d.ts","../../node_modules/.pnpm/@types+react@19.2.17/node_modules/@types/react/jsx-runtimeLine truncated
|
||||
@@ -8,7 +8,8 @@ export default defineConfig({
|
||||
rollupOptions: {
|
||||
output: {
|
||||
assetFileNames: (assetInfo) => {
|
||||
const extType = assetInfo.name.split('.')[1]
|
||||
const assetName = assetInfo.names?.[0] ?? assetInfo.name ?? ''
|
||||
const extType = assetName.split('.').pop() ?? ''
|
||||
|
||||
if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
|
||||
return `landing/img/[name]-[hash][extname]`
|
||||
|
||||
+173
-8
@@ -2431,6 +2431,149 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/sso/exchange": {
|
||||
"post": {
|
||||
"description": "Validates a third-party OIDC access token by calling the issuer's /userinfo endpoint, requires a verified email claim, then mints an Artalk session JWT. Use when the surrounding application already runs OIDC and you want Artalk comments to inherit that session without showing Artalk's own login UI.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Auth"
|
||||
],
|
||||
"summary": "Exchange an external IdP access token for an Artalk JWT",
|
||||
"operationId": "AuthSSOExchange",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "External SSO token",
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.ParamsAuthSSOExchange"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.ResponseUserLogin"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.Map"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"msg": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.Map"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"msg": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.Map"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"msg": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.Map"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"msg": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"502": {
|
||||
"description": "Bad Gateway",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.Map"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"msg": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"503": {
|
||||
"description": "Service Unavailable",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.Map"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"msg": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/stats/{type}": {
|
||||
"get": {
|
||||
"description": "Get the statistics of various data analysis",
|
||||
@@ -3566,6 +3709,24 @@ const docTemplate = `{
|
||||
"$ref": "#/definitions/handler.ResponseVote"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.Map"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"msg": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
@@ -3801,10 +3962,6 @@ const docTemplate = `{
|
||||
},
|
||||
"common.JSONResult": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"data",
|
||||
"msg"
|
||||
],
|
||||
"properties": {
|
||||
"data": {
|
||||
"description": "Data"
|
||||
@@ -3829,7 +3986,6 @@ const docTemplate = `{
|
||||
"date",
|
||||
"email_encrypted",
|
||||
"id",
|
||||
"ip_region",
|
||||
"is_allow_reply",
|
||||
"is_collapsed",
|
||||
"is_pending",
|
||||
@@ -4128,6 +4284,18 @@ const docTemplate = `{
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"handler.ParamsAuthSSOExchange": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"token"
|
||||
],
|
||||
"properties": {
|
||||
"token": {
|
||||
"description": "External IdP access token (e.g. an Auth0 access token)",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.ParamsCaptchaVerify": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -4731,7 +4899,6 @@ const docTemplate = `{
|
||||
"date",
|
||||
"email_encrypted",
|
||||
"id",
|
||||
"ip_region",
|
||||
"is_allow_reply",
|
||||
"is_collapsed",
|
||||
"is_pending",
|
||||
@@ -4854,7 +5021,6 @@ const docTemplate = `{
|
||||
"required": [
|
||||
"comments",
|
||||
"count",
|
||||
"page",
|
||||
"roots_count"
|
||||
],
|
||||
"properties": {
|
||||
@@ -4885,7 +5051,6 @@ const docTemplate = `{
|
||||
"date",
|
||||
"email_encrypted",
|
||||
"id",
|
||||
"ip_region",
|
||||
"is_allow_reply",
|
||||
"is_collapsed",
|
||||
"is_pending",
|
||||
|
||||
@@ -10,11 +10,12 @@
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"swagger:build": "npx @redocly/cli build-docs --output ./dist/http-api.html",
|
||||
"swagger:serve": "npx @redocly/cli preview-docs",
|
||||
"swagger:build-http-client": "npx swagger-typescript-api -n v2 -p ./swagger.json -o ../../ui/artalk/src/api --responses --sort-types"
|
||||
"swagger:build": "redocly build-docs --output ./dist/http-api.html",
|
||||
"swagger:serve": "redocly preview",
|
||||
"swagger:build-http-client": "swagger-typescript-api generate -n v2 -p ./swagger.json -o ../../ui/artalk/src/api --responses --sort-types && pnpm --dir ../.. exec prettier --write --ignore-path /dev/null --no-semi ui/artalk/src/api/v2.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@redocly/cli": "1.25.7"
|
||||
"@redocly/cli": "2.40.0",
|
||||
"swagger-typescript-api": "13.12.6"
|
||||
}
|
||||
}
|
||||
+11
-16
@@ -1,5 +1,3 @@
|
||||
organization: Artalk
|
||||
|
||||
extends:
|
||||
- recommended
|
||||
|
||||
@@ -7,17 +5,14 @@ apis:
|
||||
artalk@v2:
|
||||
root: ./swagger.json
|
||||
|
||||
theme:
|
||||
openapi:
|
||||
schemaExpansionLevel: 2
|
||||
defaultSampleLanguage: curl
|
||||
hideLogo: true
|
||||
generateCodeSamples:
|
||||
languages:
|
||||
- lang: curl
|
||||
- lang: Python
|
||||
- lang: Go
|
||||
- lang: Node.js
|
||||
- lang: JavaScript
|
||||
- lang: PHP
|
||||
- lang: Java
|
||||
openapi:
|
||||
schemasExpansionLevel: 2
|
||||
codeSamples:
|
||||
languages:
|
||||
- lang: curl
|
||||
- lang: Python
|
||||
- lang: Go
|
||||
- lang: Node.js
|
||||
- lang: JavaScript
|
||||
- lang: PHP
|
||||
- lang: Java
|
||||
+173
-8
@@ -2424,6 +2424,149 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/sso/exchange": {
|
||||
"post": {
|
||||
"description": "Validates a third-party OIDC access token by calling the issuer's /userinfo endpoint, requires a verified email claim, then mints an Artalk session JWT. Use when the surrounding application already runs OIDC and you want Artalk comments to inherit that session without showing Artalk's own login UI.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Auth"
|
||||
],
|
||||
"summary": "Exchange an external IdP access token for an Artalk JWT",
|
||||
"operationId": "AuthSSOExchange",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "External SSO token",
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.ParamsAuthSSOExchange"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.ResponseUserLogin"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.Map"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"msg": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.Map"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"msg": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.Map"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"msg": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.Map"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"msg": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"502": {
|
||||
"description": "Bad Gateway",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.Map"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"msg": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"503": {
|
||||
"description": "Service Unavailable",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.Map"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"msg": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/stats/{type}": {
|
||||
"get": {
|
||||
"description": "Get the statistics of various data analysis",
|
||||
@@ -3559,6 +3702,24 @@
|
||||
"$ref": "#/definitions/handler.ResponseVote"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.Map"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"msg": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
@@ -3794,10 +3955,6 @@
|
||||
},
|
||||
"common.JSONResult": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"data",
|
||||
"msg"
|
||||
],
|
||||
"properties": {
|
||||
"data": {
|
||||
"description": "Data"
|
||||
@@ -3822,7 +3979,6 @@
|
||||
"date",
|
||||
"email_encrypted",
|
||||
"id",
|
||||
"ip_region",
|
||||
"is_allow_reply",
|
||||
"is_collapsed",
|
||||
"is_pending",
|
||||
@@ -4121,6 +4277,18 @@
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"handler.ParamsAuthSSOExchange": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"token"
|
||||
],
|
||||
"properties": {
|
||||
"token": {
|
||||
"description": "External IdP access token (e.g. an Auth0 access token)",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.ParamsCaptchaVerify": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -4724,7 +4892,6 @@
|
||||
"date",
|
||||
"email_encrypted",
|
||||
"id",
|
||||
"ip_region",
|
||||
"is_allow_reply",
|
||||
"is_collapsed",
|
||||
"is_pending",
|
||||
@@ -4847,7 +5014,6 @@
|
||||
"required": [
|
||||
"comments",
|
||||
"count",
|
||||
"page",
|
||||
"roots_count"
|
||||
],
|
||||
"properties": {
|
||||
@@ -4878,7 +5044,6 @@
|
||||
"date",
|
||||
"email_encrypted",
|
||||
"id",
|
||||
"ip_region",
|
||||
"is_allow_reply",
|
||||
"is_collapsed",
|
||||
"is_pending",
|
||||
|
||||
@@ -45,9 +45,6 @@ definitions:
|
||||
msg:
|
||||
description: Message
|
||||
type: string
|
||||
required:
|
||||
- data
|
||||
- msg
|
||||
type: object
|
||||
common.Map:
|
||||
additionalProperties: true
|
||||
@@ -110,7 +107,6 @@ definitions:
|
||||
- date
|
||||
- email_encrypted
|
||||
- id
|
||||
- ip_region
|
||||
- is_allow_reply
|
||||
- is_collapsed
|
||||
- is_pending
|
||||
@@ -276,6 +272,14 @@ definitions:
|
||||
handler.Map:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
handler.ParamsAuthSSOExchange:
|
||||
properties:
|
||||
token:
|
||||
description: External IdP access token (e.g. an Auth0 access token)
|
||||
type: string
|
||||
required:
|
||||
- token
|
||||
type: object
|
||||
handler.ParamsCaptchaVerify:
|
||||
properties:
|
||||
value:
|
||||
@@ -755,7 +759,6 @@ definitions:
|
||||
- date
|
||||
- email_encrypted
|
||||
- id
|
||||
- ip_region
|
||||
- is_allow_reply
|
||||
- is_collapsed
|
||||
- is_pending
|
||||
@@ -802,7 +805,6 @@ definitions:
|
||||
required:
|
||||
- comments
|
||||
- count
|
||||
- page
|
||||
- roots_count
|
||||
type: object
|
||||
handler.ResponseCommentUpdate:
|
||||
@@ -863,7 +865,6 @@ definitions:
|
||||
- date
|
||||
- email_encrypted
|
||||
- id
|
||||
- ip_region
|
||||
- is_allow_reply
|
||||
- is_collapsed
|
||||
- is_pending
|
||||
@@ -2678,6 +2679,87 @@ paths:
|
||||
summary: Update Site
|
||||
tags:
|
||||
- Site
|
||||
/sso/exchange:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Validates a third-party OIDC access token by calling the issuer's
|
||||
/userinfo endpoint, requires a verified email claim, then mints an Artalk
|
||||
session JWT. Use when the surrounding application already runs OIDC and you
|
||||
want Artalk comments to inherit that session without showing Artalk's own
|
||||
login UI.
|
||||
operationId: AuthSSOExchange
|
||||
parameters:
|
||||
- description: External SSO token
|
||||
in: body
|
||||
name: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/handler.ParamsAuthSSOExchange'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.ResponseUserLogin'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/handler.Map'
|
||||
- properties:
|
||||
msg:
|
||||
type: string
|
||||
type: object
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/handler.Map'
|
||||
- properties:
|
||||
msg:
|
||||
type: string
|
||||
type: object
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/handler.Map'
|
||||
- properties:
|
||||
msg:
|
||||
type: string
|
||||
type: object
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/handler.Map'
|
||||
- properties:
|
||||
msg:
|
||||
type: string
|
||||
type: object
|
||||
"502":
|
||||
description: Bad Gateway
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/handler.Map'
|
||||
- properties:
|
||||
msg:
|
||||
type: string
|
||||
type: object
|
||||
"503":
|
||||
description: Service Unavailable
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/handler.Map'
|
||||
- properties:
|
||||
msg:
|
||||
type: string
|
||||
type: object
|
||||
summary: Exchange an external IdP access token for an Artalk JWT
|
||||
tags:
|
||||
- Auth
|
||||
/stats/{type}:
|
||||
get:
|
||||
consumes:
|
||||
@@ -3311,6 +3393,15 @@ paths:
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.ResponseVote'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/handler.Map'
|
||||
- properties:
|
||||
msg:
|
||||
type: string
|
||||
type: object
|
||||
"403":
|
||||
description: Forbidden
|
||||
schema:
|
||||
|
||||
+13
-13
@@ -4,7 +4,7 @@ import url from 'node:url'
|
||||
import { fixupPluginRules } from '@eslint/compat'
|
||||
import pluginCompat from 'eslint-plugin-compat'
|
||||
import eslintJs from '@eslint/js'
|
||||
import pluginTS from '@typescript-eslint/eslint-plugin'
|
||||
import { defineConfig } from 'eslint/config'
|
||||
import eslintConfigPrettier from 'eslint-config-prettier'
|
||||
import pluginImportX from 'eslint-plugin-import-x'
|
||||
import pluginReact from 'eslint-plugin-react'
|
||||
@@ -17,13 +17,11 @@ import vueParser from 'vue-eslint-parser'
|
||||
import pluginArtalk from 'eslint-plugin-artalk'
|
||||
|
||||
const __dirname = path.dirname(url.fileURLToPath(import.meta.url))
|
||||
const tsProjects = ['./tsconfig.base.json', './ui/*/tsconfig.json', './docs/*/tsconfig.json']
|
||||
|
||||
export default eslintTs.config(
|
||||
export default defineConfig(
|
||||
eslintJs.configs.recommended,
|
||||
...eslintTs.configs.recommended,
|
||||
// @ts-expect-error the type of `pluginVue` is not compatible with the latest `eslint` v9 package yet
|
||||
...pluginVue.configs['flat/recommended'],
|
||||
eslintTs.configs.recommended,
|
||||
pluginVue.configs['flat/recommended'],
|
||||
pluginCompat.configs['flat/recommended'],
|
||||
|
||||
/* Global Ignores */
|
||||
@@ -38,9 +36,12 @@ export default eslintTs.config(
|
||||
'local',
|
||||
'test',
|
||||
'**/.vitepress/cache',
|
||||
'**/.vitepress/.temp',
|
||||
'**/*.config.js',
|
||||
'**/*.config.ts',
|
||||
'**/*.d.ts',
|
||||
'**/*.d.cts',
|
||||
'**/*.d.mts',
|
||||
],
|
||||
},
|
||||
|
||||
@@ -51,7 +52,7 @@ export default eslintTs.config(
|
||||
parser: vueParser,
|
||||
parserOptions: {
|
||||
parser: eslintTs.parser,
|
||||
project: tsProjects,
|
||||
projectService: true,
|
||||
tsconfigRootDir: __dirname,
|
||||
globals: {
|
||||
...globals.browser,
|
||||
@@ -64,7 +65,6 @@ export default eslintTs.config(
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
'@typescript-eslint': pluginTS,
|
||||
'import-x': pluginImportX,
|
||||
artalk: pluginArtalk,
|
||||
},
|
||||
@@ -90,7 +90,7 @@ export default eslintTs.config(
|
||||
},
|
||||
'import-x/resolver': {
|
||||
typescript: {
|
||||
project: tsProjects,
|
||||
project: './tsconfig.json',
|
||||
},
|
||||
node: true,
|
||||
},
|
||||
@@ -146,14 +146,14 @@ export default eslintTs.config(
|
||||
...pluginReact.configs['jsx-runtime'].rules,
|
||||
...pluginReactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': 'warn',
|
||||
'react-hooks/immutability': 'off',
|
||||
'react-hooks/set-state-in-effect': 'off',
|
||||
'react/prop-types': 'off',
|
||||
},
|
||||
languageOptions: {
|
||||
...pluginReact.configs.recommended.languageOptions,
|
||||
},
|
||||
|
||||
settings: {
|
||||
react: {
|
||||
version: '18.3',
|
||||
version: '19.2',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,94 +1,95 @@
|
||||
module github.com/artalkjs/artalk/v2
|
||||
|
||||
go 1.22.7
|
||||
go 1.26.5
|
||||
|
||||
require (
|
||||
github.com/adrg/xdg v0.5.0
|
||||
github.com/adrg/xdg v0.5.3
|
||||
github.com/allegro/bigcache/v3 v3.1.0
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
||||
github.com/blang/semver v3.5.1+incompatible
|
||||
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874
|
||||
github.com/cheggaaa/pb/v3 v3.1.5
|
||||
github.com/eko/gocache/lib/v4 v4.1.6
|
||||
github.com/eko/gocache/store/bigcache/v4 v4.2.2
|
||||
github.com/eko/gocache/store/memcache/v4 v4.2.2
|
||||
github.com/eko/gocache/store/redis/v4 v4.2.2
|
||||
github.com/fatih/color v1.17.0
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.12.0
|
||||
github.com/goccy/go-yaml v1.12.0
|
||||
github.com/gofiber/fiber/v2 v2.52.5
|
||||
github.com/gofiber/swagger v1.1.0
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/bradfitz/gomemcache v0.0.0-20260422231931-4d751bb6e37c
|
||||
github.com/cheggaaa/pb/v3 v3.2.0
|
||||
github.com/eko/gocache/lib/v4 v4.2.3
|
||||
github.com/eko/gocache/store/bigcache/v4 v4.2.4
|
||||
github.com/eko/gocache/store/memcache/v4 v4.2.4
|
||||
github.com/eko/gocache/store/redis/v4 v4.2.6
|
||||
github.com/fatih/color v1.19.0
|
||||
github.com/go-sql-driver/mysql v1.10.0
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.19.0
|
||||
github.com/goccy/go-yaml v1.19.2
|
||||
github.com/gofiber/fiber/v2 v2.52.14
|
||||
github.com/gofiber/swagger v1.1.1
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/iancoleman/strcase v0.3.0
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.0
|
||||
github.com/jedib0t/go-pretty/v6 v6.8.3
|
||||
github.com/jeremywohl/flatten v1.0.1
|
||||
github.com/knadh/koanf v1.5.0
|
||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20240510055607-89e20ab7b6c6
|
||||
github.com/markbates/goth v1.80.0
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
github.com/libtnb/sqlite v1.2.1
|
||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260709160242-cd40e3a1d532
|
||||
github.com/markbates/goth v1.82.0
|
||||
github.com/mattn/go-colorable v0.1.15
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/nikoksr/notify v1.0.0
|
||||
github.com/nikoksr/notify v1.5.0
|
||||
github.com/qwqcode/go-aliyun-email v0.0.0-20180120030821-cb6e7b1382bf
|
||||
github.com/redis/go-redis/v9 v9.6.1
|
||||
github.com/redis/go-redis/v9 v9.21.0
|
||||
github.com/rhysd/go-github-selfupdate v1.2.3
|
||||
github.com/samber/lo v1.47.0
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/samber/lo v1.53.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/steambap/captcha v1.4.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/swaggo/swag v1.16.3
|
||||
github.com/tidwall/gjson v1.18.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/swaggo/swag v1.16.6
|
||||
github.com/tidwall/gjson v1.19.0
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible
|
||||
github.com/yuin/goldmark v1.7.4
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.27.0
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/term v0.24.0
|
||||
golang.org/x/text v0.18.0
|
||||
github.com/yuin/goldmark v1.8.4
|
||||
go.uber.org/zap v1.28.0
|
||||
golang.org/x/crypto v0.54.0
|
||||
golang.org/x/exp v0.0.0-20260718201538-764159d718ef
|
||||
golang.org/x/sync v0.22.0
|
||||
golang.org/x/term v0.45.0
|
||||
golang.org/x/text v0.40.0
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/mysql v1.5.7
|
||||
gorm.io/driver/postgres v1.5.9
|
||||
gorm.io/driver/sqlite v1.5.6
|
||||
gorm.io/driver/sqlserver v1.5.3
|
||||
gorm.io/gorm v1.25.12
|
||||
gorm.io/driver/mysql v1.6.0
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
gorm.io/driver/sqlserver v1.6.3
|
||||
gorm.io/gorm v1.31.2
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.5.2 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/ClickHouse/ch-go v0.62.0 // indirect
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.29.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
filippo.io/edwards25519 v1.2.0 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/VividCortex/ewma v1.2.0 // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/andybalholm/brotli v1.2.2 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blinkbean/dingtalk v1.1.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-faster/city v1.0.1 // indirect
|
||||
github.com/go-faster/errors v0.7.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.10.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v1.0.0 // indirect
|
||||
github.com/go-openapi/jsonreference v1.0.0 // indirect
|
||||
github.com/go-openapi/spec v0.22.9 // indirect
|
||||
github.com/go-openapi/swag/conv v0.27.3 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.27.3 // indirect
|
||||
github.com/go-openapi/swag/loading v0.27.3 // indirect
|
||||
github.com/go-openapi/swag/pools v0.27.3 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.27.3 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.27.3 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.27.3 // indirect
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/goccy/go-json v0.10.6 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/go-github/v30 v30.1.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/go-querystring v1.2.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
@@ -96,67 +97,65 @@ require (
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.1 // indirect
|
||||
github.com/jackc/pgx/v5 v5.10.0 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.17.10 // indirect
|
||||
github.com/klauspost/compress v1.19.1 // indirect
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.4 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
github.com/lestrrat-go/iter v1.0.2 // indirect
|
||||
github.com/lestrrat-go/jwx v1.2.30 // indirect
|
||||
github.com/lestrrat-go/jwx v1.2.31 // indirect
|
||||
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||
github.com/line/line-bot-sdk-go v7.8.0+incompatible // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/markbates/going v1.0.3 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.23 // indirect
|
||||
github.com/microsoft/go-mssqldb v1.7.2 // indirect
|
||||
github.com/mattn/go-isatty v0.0.23 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.24 // indirect
|
||||
github.com/microsoft/go-mssqldb v1.10.0 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/onsi/gomega v1.34.2 // indirect
|
||||
github.com/paulmach/orb v0.11.1 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/onsi/gomega v1.40.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.20.4 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.60.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.24.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.70.1 // indirect
|
||||
github.com/prometheus/procfs v0.21.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/slack-go/slack v0.14.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/swaggo/files/v2 v2.0.1 // indirect
|
||||
github.com/slack-go/slack v0.27.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
github.com/swaggo/files/v2 v2.0.2 // indirect
|
||||
github.com/tcnksm/go-gitconfig v0.1.2 // indirect
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/match v1.2.0 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
||||
github.com/ulikunitz/xz v0.5.16 // indirect
|
||||
github.com/utahta/go-linenotify v0.5.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.56.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.72.0 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
go.opentelemetry.io/otel v1.30.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.30.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/image v0.20.0 // indirect
|
||||
golang.org/x/net v0.29.0 // indirect
|
||||
golang.org/x/oauth2 v0.23.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/tools v0.25.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/image v0.44.0 // indirect
|
||||
golang.org/x/mod v0.38.0 // indirect
|
||||
golang.org/x/net v0.57.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/tools v0.48.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
modernc.org/libc v1.74.3 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
modernc.org/sqlite v1.54.0 // indirect
|
||||
)
|
||||
@@ -1,44 +1,40 @@
|
||||
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/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
|
||||
cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 h1:jHb/wfvRikGdxMXYV3QG/SzUOPYN9KEUUuC0Yd0/vC0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1/go.mod h1:pzBXCYn05zvYIrwLgtK8Ap8QcjRg+0i76tMQdWN6wOk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0/go.mod h1:Q28U+75mpCaSCDowNEmhIo/rmgdkqmkmzI7N6TGR4UY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0/go.mod h1:cw4zVQgBby0Z5f2v0itn6se2dDP17nTjbZFXW5uPyHA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 h1:E4MgwLBGeVB5f2MdcIVD3ELVAWpr+WD6MUe1i+tM/PA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0/go.mod h1:Y2b/1clN4zsAoUd/pgNAQHjLDnTis/6ROkUfyob6psM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/ClickHouse/ch-go v0.62.0 h1:eXH0hytXeCEEZHgMvOX9IiW7wqBb4w1MJMp9rArbkrc=
|
||||
github.com/ClickHouse/ch-go v0.62.0/go.mod h1:uzso52/PD9+gZj7tL6XAo8/EYDrx7CIwNF4c6PnO6S0=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.29.0 h1:Dj1w59RssRyLgGHXtYaWU0eIM1pJsu9nGPi/btmvAqw=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.29.0/go.mod h1:bLookq6qZJ4Ush/6tOAnJGh1Sf3Sa/nQoMn71p7ZCUE=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
|
||||
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
|
||||
github.com/adrg/xdg v0.5.0 h1:dDaZvhMXatArP1NPHhnfaQUqWBLBsmx1h1HXQdMoFCY=
|
||||
github.com/adrg/xdg v0.5.0/go.mod h1:dDdY4M4DF9Rjy4kHPeNL+ilVF+p2lK8IdM9/rTSGcI4=
|
||||
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
||||
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||
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=
|
||||
@@ -46,8 +42,8 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/allegro/bigcache/v3 v3.1.0 h1:H2Vp8VOvxcrB91o86fUSVJFqeuz8kpyyB02eH3bSzwk=
|
||||
github.com/allegro/bigcache/v3 v3.1.0/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/andybalholm/brotli v1.2.2 h1:HzTuoo2ErYQqf5qvcJInB8uvqSVxRttzkFexPWtnceM=
|
||||
github.com/andybalholm/brotli v1.2.2/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
|
||||
@@ -78,8 +74,8 @@ github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdn
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/blinkbean/dingtalk v1.1.3 h1:MbidFZYom7DTFHD/YIs+eaI7kRy52kmWE/sy0xjo6E4=
|
||||
github.com/blinkbean/dingtalk v1.1.3/go.mod h1:9BaLuGSBqY3vT5hstValh48DbsKO7vaHaJnG9pXwbto=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20260422231931-4d751bb6e37c h1:6Gpm9YYUEQx2T9zMsYolQhr6sjwwGtFitSA0pQsa7a8=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20260422231931-4d751bb6e37c/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
@@ -88,34 +84,36 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheggaaa/pb/v3 v3.1.5 h1:QuuUzeM2WsAqG2gMqtzaWithDJv0i+i6UlnwSCI4QLk=
|
||||
github.com/cheggaaa/pb/v3 v3.1.5/go.mod h1:CrxkeghYTXi1lQBEI7jSn+3svI3cuc19haAj6jM60XI=
|
||||
github.com/cheggaaa/pb/v3 v3.2.0 h1:ziC7JV5/Ge2iZNa9ckxdXBxHHyPgC+p/QGzhWRoPlHU=
|
||||
github.com/cheggaaa/pb/v3 v3.2.0/go.mod h1:KtXGzgipYGqY3avGtFmlQTgiT88AEFvc1LvPk7oA9fM=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
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/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||
github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw=
|
||||
github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo=
|
||||
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/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/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3hK/4HUq48LQ6Wwqo=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
|
||||
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eko/gocache/lib/v4 v4.1.6 h1:5WWIGISKhE7mfkyF+SJyWwqa4Dp2mkdX8QsZpnENqJI=
|
||||
github.com/eko/gocache/lib/v4 v4.1.6/go.mod h1:HFxC8IiG2WeRotg09xEnPD72sCheJiTSr4Li5Ameg7g=
|
||||
github.com/eko/gocache/store/bigcache/v4 v4.2.2 h1:zS8wjE/MqNQZOZMa19urItNIiVPE1CktJpmaGhPv9yE=
|
||||
github.com/eko/gocache/store/bigcache/v4 v4.2.2/go.mod h1:B3EPikcLx486f5Xw3YtFjm3oWnKGYngxJW4v1/n5L5g=
|
||||
github.com/eko/gocache/store/memcache/v4 v4.2.2 h1:VKfxytQ5bkcfF3LhmgkrqRiEU2yCN2/rJBUvF1fKZJw=
|
||||
github.com/eko/gocache/store/memcache/v4 v4.2.2/go.mod h1:9lFU3tZPiej8E3J4ueZ0K9kIdiDQpRxu6WhtId5OsZA=
|
||||
github.com/eko/gocache/store/redis/v4 v4.2.2 h1:Thw31fzGuH3WzJywsdbMivOmP550D6JS7GDHhvCJPA0=
|
||||
github.com/eko/gocache/store/redis/v4 v4.2.2/go.mod h1:LaTxLKx9TG/YUEybQvPMij++D7PBTIJ4+pzvk0ykz0w=
|
||||
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/eko/gocache/lib/v4 v4.2.3 h1:s78TFqEGAH3SbzP4N40D755JYT/aaGFKEPrsUtC1chU=
|
||||
github.com/eko/gocache/lib/v4 v4.2.3/go.mod h1:Zus8mwmaPu1VYOzfomb+Dvx2wV7fT5jDRbHYtQM6MEY=
|
||||
github.com/eko/gocache/store/bigcache/v4 v4.2.4 h1:Ak2qZYh7ioRoSts6xUObdnbNhKIZQxrVGO1kb21U8Ak=
|
||||
github.com/eko/gocache/store/bigcache/v4 v4.2.4/go.mod h1:ty85W3DT2S5ZO3VG30UkwMQ1FmH8p6O0Y6FrQz7alUo=
|
||||
github.com/eko/gocache/store/memcache/v4 v4.2.4 h1:k8bLwU7F3caEO43eC2dHZR0X+KsoF/aQfpsRgFap6Io=
|
||||
github.com/eko/gocache/store/memcache/v4 v4.2.4/go.mod h1:DxmvRb6CIb8GKnNFgn+A+hhAEeFZRxThLo9+v3Z6TMs=
|
||||
github.com/eko/gocache/store/redis/v4 v4.2.6 h1:btD3TIFAgujZAsvTEBTaDfNTSAf+GO32il0HQ5MIqCI=
|
||||
github.com/eko/gocache/store/redis/v4 v4.2.6/go.mod h1:0PMef3sy4AonKqrxdnUsIKDAMtqNyJI4e6asTo00XrE=
|
||||
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=
|
||||
@@ -123,18 +121,14 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m
|
||||
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.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
|
||||
github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=
|
||||
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
|
||||
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
@@ -142,50 +136,59 @@ github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp
|
||||
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-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
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.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s=
|
||||
github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y=
|
||||
github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY=
|
||||
github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0=
|
||||
github.com/go-openapi/spec v0.22.9 h1:/vKIFDcGKp0ktZWGbym/tJEWbk6/XOEmAVU0kqKMH+w=
|
||||
github.com/go-openapi/spec v0.22.9/go.mod h1:b/mNUYIOQOyIiUzUzXEE8xzyZqf93KvM9hQGP91yfl0=
|
||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||
github.com/go-openapi/swag/conv v0.27.3 h1:iqJFmGEjmX3AY0lSszABFqRVqOSt99XS0LzNIMJYuhU=
|
||||
github.com/go-openapi/swag/conv v0.27.3/go.mod h1:nPRmN6jgNme99hpf+nM0auDZGALWIqlwhisKPK/bQhQ=
|
||||
github.com/go-openapi/swag/jsonutils v0.27.3 h1:1DEz+O82frtSMBcos/7XIn1GnpNTbsD4Bru4Dc/uhRc=
|
||||
github.com/go-openapi/swag/jsonutils v0.27.3/go.mod h1:qiDCoQvzkMxrV3G8FLEdIU5L+EFYc0zcDOHWT3Yofvo=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.3 h1:h/eT9kmGCDdFLJF29lOhzLtF0FmP1AX2MhLJWVebsb8=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.3/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY=
|
||||
github.com/go-openapi/swag/loading v0.27.3 h1:L9nQkEgzU7QgFQL+pLEMfGUKxeM4pWwGwbET9Z3weW0=
|
||||
github.com/go-openapi/swag/loading v0.27.3/go.mod h1:rJ0NeaKsF4CVPnMGjPQl7JlSHzvD0bc2DKXLss1hiuE=
|
||||
github.com/go-openapi/swag/pools v0.27.3 h1:gXjImP3F6/56wRRcFgEPld084Y6u2gs21ikPBt8NKBk=
|
||||
github.com/go-openapi/swag/pools v0.27.3/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE=
|
||||
github.com/go-openapi/swag/stringutils v0.27.3 h1:Ru28hnbAvN5wycALQYy8IobHvASq+FUFMlp1QzLM0JI=
|
||||
github.com/go-openapi/swag/stringutils v0.27.3/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM=
|
||||
github.com/go-openapi/swag/typeutils v0.27.3 h1:l6SSrx5eR5/WVwrGNzN6bQ9WqL04mrxNBl9YgQ3rcJ4=
|
||||
github.com/go-openapi/swag/typeutils v0.27.3/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ=
|
||||
github.com/go-openapi/swag/yamlutils v0.27.3 h1:cRFCAoYtslYn9L9T0xWryHy1t7c1MACC+DMj3CLvwvs=
|
||||
github.com/go-openapi/swag/yamlutils v0.27.3/go.mod h1:6JYBGj8sw/NawMllyZY+cTA8Mzk2etS3ZBASdcyPsiU=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo=
|
||||
github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug=
|
||||
github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
|
||||
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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
|
||||
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
|
||||
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.12.0 h1:Ew0+c2o1mXSUqMwjuNup3MK/vw1HkLS3ILljX5C6lVE=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.12.0/go.mod h1:13F0m6/DtqqSDso9IAVuhbZ4I7AiRAHrolmDMu9v5vY=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM=
|
||||
github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
|
||||
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.19.0 h1:/Y0bars250zggm+1A2PvwaJQsJel7/tS4D/Hhwt66Bc=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.19.0/go.mod h1:4/hVAuX2As0/ej3fLuAd+IvoCXV7/h2cj5nInI11uxM=
|
||||
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
||||
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
|
||||
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
||||
github.com/gofiber/swagger v1.1.0 h1:ff3rg1fB+Rp5JN/N8jfxTiZtMKe/9tB9QDc79fPiJKQ=
|
||||
github.com/gofiber/swagger v1.1.0/go.mod h1:pRZL0Np35sd+lTODTE5The0G+TMHfNY+oC4hM2/i5m8=
|
||||
github.com/gofiber/fiber/v2 v2.52.14 h1:Of3L+9qVFaQNwPlcmEdl5IIodHz8BSE0j37R7rWu4pE=
|
||||
github.com/gofiber/fiber/v2 v2.52.14/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||
github.com/gofiber/swagger v1.1.1 h1:FZVhVQQ9s1ZKLHL/O0loLh49bYB5l1HEAgxDlcTtkRA=
|
||||
github.com/gofiber/swagger v1.1.1/go.mod h1:vtvY/sQAMc/lGTUCg0lqmBL7Ht9O7uzChpbvJeJQINw=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-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.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
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-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||
@@ -194,8 +197,6 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF0
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.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=
|
||||
@@ -219,19 +220,21 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.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.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo=
|
||||
github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
|
||||
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v0.0.0-20171113160352-8c31c18f31ed/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/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -247,7 +250,6 @@ github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1 h1:LqbZZ9sNMWVjeXS4NN5
|
||||
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
@@ -277,7 +279,10 @@ github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/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=
|
||||
@@ -297,24 +302,12 @@ github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7V
|
||||
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jackc/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 v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
|
||||
github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
|
||||
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/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
|
||||
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
|
||||
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||
github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA=
|
||||
github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
|
||||
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
|
||||
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
|
||||
github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
|
||||
github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da h1:FjHUJJ7oBW4G/9j1KzlHaXL09LyMVM9rupS39lncbXk=
|
||||
@@ -325,8 +318,8 @@ github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nD
|
||||
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.0 h1:wmZVuAcEkZRT+Aq1xXpE8IGat4vE5WXOMmBpbQqERXw=
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.0/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E=
|
||||
github.com/jedib0t/go-pretty/v6 v6.8.3 h1:yVSk5aemoYHCvcrtqyXklwqcgHQIQzmy/oUzFlmffSQ=
|
||||
github.com/jedib0t/go-pretty/v6 v6.8.3/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
|
||||
github.com/jeremywohl/flatten v1.0.1 h1:LrsxmB3hfwJuE+ptGOijix1PIfOoKLJ3Uee/mzbgtrs=
|
||||
github.com/jeremywohl/flatten v1.0.1/go.mod h1:4AmD/VxjWcI5SRB0n6szE2A6s2fsNHDLO0nAlMHgfLQ=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
@@ -335,13 +328,10 @@ 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.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
|
||||
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.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
@@ -350,9 +340,10 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
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.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.17.10 h1:oXAz+Vh0PMUvJczoi+flxpnBEPxoER1IaAnU/NMPtT0=
|
||||
github.com/klauspost/compress v1.17.10/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
|
||||
github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
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 v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs=
|
||||
github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@@ -360,6 +351,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -368,57 +360,52 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/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/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
|
||||
github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k=
|
||||
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
|
||||
github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
|
||||
github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
|
||||
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
|
||||
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
|
||||
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
|
||||
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
|
||||
github.com/lestrrat-go/jwx v1.2.30 h1:VKIFrmjYn0z2J51iLPadqoHIVLzvWNa1kCsTqNDHYPA=
|
||||
github.com/lestrrat-go/jwx v1.2.30/go.mod h1:vMxrwFhunGZ3qddmfmEm2+uced8MSI6QFWGTKygjSzQ=
|
||||
github.com/lestrrat-go/jwx v1.2.31 h1:/OM9oNl/fzyldpv5HKZ9m7bTywa7COUfg8gujd9nJ54=
|
||||
github.com/lestrrat-go/jwx v1.2.31/go.mod h1:eQJKoRwWcLg4PfD5CFA5gIZGxhPgoPYq9pZISdxLf0c=
|
||||
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
||||
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/libtnb/sqlite v1.2.1 h1:lrhN3yG49pj1gmtMMP6Es0lhKIkzDeqBul2EWOLyjpY=
|
||||
github.com/libtnb/sqlite v1.2.1/go.mod h1:pGq13BirCjkM6ihkWWxs0PGFhBiwUyA0PqdSmgDWfmM=
|
||||
github.com/line/line-bot-sdk-go v7.8.0+incompatible h1:Uf9/OxV0zCVfqyvwZPH8CrdiHXXmMRa/L91G3btQblQ=
|
||||
github.com/line/line-bot-sdk-go v7.8.0+incompatible/go.mod h1:0RjLjJEAU/3GIcHkC3av6O4jInAbt25nnZVmOFUgDBg=
|
||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20240510055607-89e20ab7b6c6 h1:YeIGErDiB/fhmNsJy0cfjoT8XnRNT9hb19xZ4MvWQDU=
|
||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20240510055607-89e20ab7b6c6/go.mod h1:C5LA5UO2ZXJrLaPLYtE1wUJMiyd/nwWaCO5cw/2pSHs=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260709160242-cd40e3a1d532 h1:jm6X6eUeR7CI5gFT6zMUtANhyvBiN0U1fWoANQ7e690=
|
||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260709160242-cd40e3a1d532/go.mod h1:sj5LMpsqB4IWdwIrcmmBJM6m+rW/uOQLSGUPhKkqdh8=
|
||||
github.com/markbates/going v1.0.3 h1:mY45T5TvW+Xz5A6jY7lf4+NLg9D8+iuStIHyR7M8qsE=
|
||||
github.com/markbates/going v1.0.3/go.mod h1:fQiT6v6yQar9UD6bd/D4Z5Afbk9J6BBVBtLiyY4gp2o=
|
||||
github.com/markbates/goth v1.80.0 h1:NnvatczZDzOs1hn9Ug+dVYf2Viwwkp/ZDX5K+GLjan8=
|
||||
github.com/markbates/goth v1.80.0/go.mod h1:4/GYHo+W6NWisrMPZnq0Yr2Q70UntNLn7KXEFhrIdAY=
|
||||
github.com/markbates/goth v1.82.0 h1:8j/c34AjBSTNzO7zTsOyP5IYCQCMBTRBHAbBt/PI0bQ=
|
||||
github.com/markbates/goth v1.82.0/go.mod h1:/DRlcq0pyqkKToyZjsL2KgiA1zbF1HIjE7u2uC79rUk=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
|
||||
github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.23 h1:cYwCQTQf3HB6xUC+BtyCLZNr7IzbOmoZbmssVNzSyiQ=
|
||||
github.com/mattn/go-isatty v0.0.23/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0=
|
||||
github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
|
||||
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
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/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||
github.com/microsoft/go-mssqldb v1.6.0/go.mod h1:00mDtPbeQCRGC1HwOOR5K/gr30P1NcEG0vx6Kbv2aJU=
|
||||
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/microsoft/go-mssqldb v1.8.2/go.mod h1:vp38dT33FGfVotRiTmDo3bFyaHq+p3LektQrjTULowo=
|
||||
github.com/microsoft/go-mssqldb v1.10.0 h1:pHEt+Qz6YFPWqREq10mqSE524QQo+/QremwTCQht7TY=
|
||||
github.com/microsoft/go-mssqldb v1.10.0/go.mod h1:mnG7lGa9iYJbzJqGCXyuQCegStKMr3kogDLD6+bmggg=
|
||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
@@ -442,7 +429,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
|
||||
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/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 h1:j2kD3MT1z4PXCiUllUJF9mWUESr9TWKS7iEKsQ/IipM=
|
||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
|
||||
@@ -450,105 +436,106 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
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/nikoksr/notify v1.0.0 h1:qe9/6FRsWdxBgQgWcpvQ0sv8LRGJZDpRB4TkL2uNdO8=
|
||||
github.com/nikoksr/notify v1.0.0/go.mod h1:hPaaDt30d6LAA7/5nb0e48Bp/MctDfycCSs8VEgN29I=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/nikoksr/notify v1.5.0 h1:mzkCw8eb0P+qHwgmGQyPPGqz4GH+07FJDr44Bs16T9k=
|
||||
github.com/nikoksr/notify v1.5.0/go.mod h1:CEV9Bw9Y59K5oj7d8h83Xl32ATeL43ZEg9qTQsfwcCc=
|
||||
github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
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/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
|
||||
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
|
||||
github.com/onsi/gomega v1.40.0 h1:Vtol0e1MghCD2ZVIilPDIg44XSL9l2QAn8ZNaljWcJc=
|
||||
github.com/onsi/gomega v1.40.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
|
||||
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
|
||||
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
|
||||
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
|
||||
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1-0.20161029093637-248dadf4e906/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI=
|
||||
github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI=
|
||||
github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
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.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA=
|
||||
github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
|
||||
github.com/prometheus/common v0.70.1 h1:1HvjP4D5oL3t8RsPlwxA9onvvStjtIHYE5XuuwOi/PY=
|
||||
github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI=
|
||||
github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY=
|
||||
github.com/qwqcode/go-aliyun-email v0.0.0-20180120030821-cb6e7b1382bf h1:FH9O7I17HCdVYbK1IMxTGgD3bVuTzyO45bR94iE+T/U=
|
||||
github.com/qwqcode/go-aliyun-email v0.0.0-20180120030821-cb6e7b1382bf/go.mod h1:5N3D7E1/M3G95YK4jPjcPRf/A7XBis0ni4VMmcVnnRY=
|
||||
github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
|
||||
github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
|
||||
github.com/redis/go-redis/v9 v9.21.0 h1:FPBE4hhbAke+TLmcY3WkpbDffJEomdqPn3HYiqAtL9E=
|
||||
github.com/redis/go-redis/v9 v9.21.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
|
||||
github.com/rhysd/go-github-selfupdate v1.2.3 h1:iaa+J202f+Nc+A8zi75uccC8Wg3omaM7HDeimXA22Ag=
|
||||
github.com/rhysd/go-github-selfupdate v1.2.3/go.mod h1:mp/N8zj6jFfBQy/XMYoWsmfzxazpPAODuqarmPDe2Rg=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
|
||||
github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
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/slack-go/slack v0.14.0 h1:6c0UTfbRnvRssZUsZ2qe0Iu07VAMPjRqOa6oX8ewF4k=
|
||||
github.com/slack-go/slack v0.14.0/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/slack-go/slack v0.27.0 h1:VWOpUzOK6UAPCCQlFxl79jhv8a/b+GOSJMnWziDJ8B8=
|
||||
github.com/slack-go/slack v0.27.0/go.mod h1:UEe+jmo9WLlwHB04qsOrTDvqM7Aa4rQL3O5wF3n0hx4=
|
||||
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.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
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/steambap/captcha v1.4.1 h1:OmMdxLCWCqJvsFaFYwRpvMckIuvI6s8s1LsBrBw97P0=
|
||||
github.com/steambap/captcha v1.4.1/go.mod h1:oC9T7IfEgnrhzjDz5Djf1H7GPffCzRMbsQfFkJmhlnk=
|
||||
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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
@@ -558,70 +545,69 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
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/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/swaggo/files/v2 v2.0.1 h1:XCVJO/i/VosCDsJu1YLpdejGsGnBE9deRMpjN4pJLHk=
|
||||
github.com/swaggo/files/v2 v2.0.1/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
|
||||
github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
|
||||
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
|
||||
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/swaggo/files/v2 v2.0.2 h1:Bq4tgS/yxLB/3nwOMcul5oLEUKa877Ykgz3CJMVbQKU=
|
||||
github.com/swaggo/files/v2 v2.0.2/go.mod h1:TVqetIzZsO9OhHX1Am9sRf9LdrFZqoK49N37KON/jr0=
|
||||
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
||||
github.com/tcnksm/go-gitconfig v0.1.2 h1:iiDhRitByXAEyjgBqsKi9QU4o2TNtv9kPP3RgPgXBPw=
|
||||
github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4UsLGSItWYCpE=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU=
|
||||
github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc=
|
||||
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
||||
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
||||
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.16 h1:ld6NyySjx5lowVKwJvMRLnW5nxKX/xnpSiFYZ/Lxur0=
|
||||
github.com/ulikunitz/xz v0.5.16/go.mod h1:H9Rt/W6/Qj27PGauhQc6nfCDy7vHpzsOThBSaYDoEhw=
|
||||
github.com/utahta/go-linenotify v0.5.0 h1:E1tJaB/XhqRY/iz203FD0MaHm10DjQPOq5/Mem2A3Gs=
|
||||
github.com/utahta/go-linenotify v0.5.0/go.mod h1:KsvBXil2wx+ByaCR0e+IZKTbp4pDesc7yjzRigLf6pE=
|
||||
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.56.0 h1:bEZdJev/6LCBlpdORfrLu/WOZXXxvrUQSiyniuaoW8U=
|
||||
github.com/valyala/fasthttp v1.56.0/go.mod h1:sReBt3XZVnudxuLOx4J/fMrJVorWRiWY2koQKgABiVI=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/valyala/fasthttp v1.72.0 h1:R7kYdoWhn1ye1fVpP+cDHDJwYm3NkwLliwgzJ/Abg7M=
|
||||
github.com/valyala/fasthttp v1.72.0/go.mod h1:zsbLTYqcpIktdQytlVBwIjY9La5d6bs990nBxWg8efk=
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg=
|
||||
github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
github.com/yuin/goldmark v1.8.4 h1:oat/nd3U6NeQqFEL3xpEJq7d7c86NI+DbSNGAs4xnjA=
|
||||
github.com/yuin/goldmark v1.8.4/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
|
||||
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
|
||||
go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts=
|
||||
go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc=
|
||||
go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc=
|
||||
go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
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.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
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.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
@@ -629,19 +615,24 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
|
||||
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
|
||||
golang.org/x/exp v0.0.0-20260718201538-764159d718ef h1:LkZ48HFgy/TvhTI0bcWkjgFkgLyKUwcTbDjS0DUjw+A=
|
||||
golang.org/x/exp v0.0.0-20260718201538-764159d718ef/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q=
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
|
||||
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=
|
||||
golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I=
|
||||
golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
@@ -652,8 +643,12 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
|
||||
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
|
||||
golang.org/x/net v0.0.0-20171115151908-9dfe39835686/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -670,26 +665,32 @@ golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
|
||||
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.0.0-20171101214715-fd80eb99c8f6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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=
|
||||
@@ -702,8 +703,12 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-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=
|
||||
@@ -733,29 +738,45 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-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-20210616045830-e2b7044e8c71/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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
||||
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
@@ -767,9 +788,15 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -781,18 +808,17 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
|
||||
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
|
||||
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@@ -823,9 +849,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
@@ -854,18 +879,45 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
|
||||
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
|
||||
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||
gorm.io/driver/sqlserver v1.5.3 h1:rjupPS4PVw+rjJkfvr8jn2lJ8BMhT4UW5FwuJY0P3Z0=
|
||||
gorm.io/driver/sqlserver v1.5.3/go.mod h1:B+CZ0/7oFJ6tAlefsKoyxdgDCXJKSgwS2bMOQZT0I00=
|
||||
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
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.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||
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.6.3 h1:UR+nWCuphPnq7UxnL57PSrlYjuvs+sf1N59GgFX7uAI=
|
||||
gorm.io/driver/sqlserver v1.6.3/go.mod h1:VZeNn7hqX1aXoN5TPAFGWvxWG90xtA8erGn2gQmpc6U=
|
||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo=
|
||||
gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI=
|
||||
modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU=
|
||||
modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI=
|
||||
modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.74.3 h1:a4J+Z8aVaxPyjyxRAdJzw246PqpcFGvVPnfT/AuM5Ws=
|
||||
modernc.org/libc v1.74.3/go.mod h1:4H7h/MJ8wnjL8RAbp9v3OXgnk22X7MouHIhDbvP3gj4=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.54.0 h1:JCxR4qwkJvOaqAoYcgDoO25Nc+ROg6EJ2LfBVzdrgog=
|
||||
modernc.org/sqlite v1.54.0/go.mod h1:4ntCLuNmnH8+GNqjka1wNg7KJd5/Hi5FYp8K+XQ7GZw=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
"Account": "Hesap"
|
||||
"Admin": "Yönetici"
|
||||
"Admin access required": "Yönetici erişimi gerekli"
|
||||
"Cannot reply to this comment": "Bu yoruma cevap verilemez"
|
||||
"Captcha required": "Doğrulama (Captcha) gerekli"
|
||||
"Checking for updates": "Güncellemeler kontrol ediliyor"
|
||||
"Comment": "Yorum"
|
||||
"Comment count": "Yorum sayısı"
|
||||
"Comment failed": "Yorum başarısız"
|
||||
"Config file read failed": "Yapılandırma dosyası okunamadı"
|
||||
"Confirm to continue?": "Devam etmek istiyor musunuz?"
|
||||
"Contains invalid URL": "Geçersiz URL içeriyor"
|
||||
"Create admin account": "Yönetici hesabı oluştur"
|
||||
"Current version is the latest": "Mevcut sürüm güncel"
|
||||
"Downloading": "İndiriliyor"
|
||||
"Email": "E-posta"
|
||||
"Enabled": "Etkin"
|
||||
"Enter {{name}}": "{{name}} girin"
|
||||
"Export complete": "Dışa aktarma tamamlandı"
|
||||
"Export error": "Dışa aktarma hatası"
|
||||
"File": "Dosya"
|
||||
"First comment": "İlk yorum"
|
||||
"Image exceeds {{file_size}} limit": "Görsel {{file_size}} sınırını aşıyor"
|
||||
"Image upload forbidden": "Görsel yükleme yasak"
|
||||
"Import completed": "İçe aktarma tamamlandı"
|
||||
"Import failed": "İçe aktarma başarısız"
|
||||
"Importing": "İçe aktarılıyor"
|
||||
"Invalid {{name}}": "Geçersiz {{name}}"
|
||||
"Link": "Bağlantı"
|
||||
"Login failed": "Giriş başarısız"
|
||||
"Login required": "Giriş gerekli"
|
||||
"Name": "Ad"
|
||||
"New version available": "Yeni sürüm mevcut"
|
||||
"No comment": "Yorum yok"
|
||||
"Notify": "Bildir"
|
||||
"Page": "Sayfa"
|
||||
"Page fetch failed": "Sayfa getirme başarısız"
|
||||
"Parent comment": "Üst yorum"
|
||||
"Password": "Şifre"
|
||||
"Password is incorrect": "Şifre yanlış"
|
||||
"Password update failed": "Şifre güncelleme başarısız"
|
||||
"Password updated": "Şifre güncellendi"
|
||||
"Pending": "Beklemede"
|
||||
"Please review": "Lütfen inceleyin"
|
||||
"Reply": "Yanıtla"
|
||||
"Restart failed: {{err}}": "Yeniden başlatma başarısız: {{err}}"
|
||||
"Retype {{name}}": "{{name}} tekrar girin"
|
||||
"Save failed": "Kaydetme başarısız"
|
||||
"Services restart complete": "Hizmetlerin yeniden başlatılması tamamlandı"
|
||||
"Site": "Site"
|
||||
"Site `{{name}}` not found. Please create it in control center.": "`{{name}}` sitesi bulunamadı. Lütfen kontrol merkezinde oluşturun."
|
||||
"Site name": "Site adı"
|
||||
"Sub-comment": "Alt yorum"
|
||||
"Target Site": "Hedef Site"
|
||||
"Task executing in background, please wait...": "İşlem arka planda yürütülüyor, lütfen bekleyin..."
|
||||
"Task in progress, please wait a moment": "İşlem devam ediyor, lütfen biraz bekleyin"
|
||||
"Type": "Tür"
|
||||
"URL Resolver": "URL Çözücü"
|
||||
"Unspecified": "Belirtilmemiş"
|
||||
"Unsupported formats": "Desteklenmeyen formatlar"
|
||||
"Update complete": "Güncelleme tamamlandı"
|
||||
"Update failed": "Güncelleme başarısız"
|
||||
"Upload image via {{method}} failed": "{{method}} üzerinden görsel yükleme başarısız"
|
||||
"User": "Kullanıcı"
|
||||
"User not found": "Kullanıcı bulunamadı"
|
||||
"Username": "Kullanıcı adı"
|
||||
"Verification failed": "Doğrulama başarısız"
|
||||
"Wrong captcha": "Yanlış doğrulama (Captcha)"
|
||||
"Your Code - {{code}}": "Kodunuz - {{code}}"
|
||||
"Your authentication token has expired. Please try signing in again.": "Kimlik doğrulama anahtarınızın süresi doldu. Lütfen tekrar giriş yapmayı deneyin."
|
||||
"Your code is: {{code}}. Use it to verify your email and sign in Artalk. If you didn't request this, simply ignore this message.": "Kodunuz: {{code}}. E-postanızı doğrulamak ve Artalk'a giriş yapmak için kullanın. Bunu siz istemediyseniz bu mesajı görmezden gelebilirsiniz."
|
||||
"{{count}} items imported": "{{count}} öğe içe aktarıldı"
|
||||
"{{done}} of {{total}} done": "{{done}} / {{total}} tamamlandı"
|
||||
"{{name}} already exists": "{{name}} zaten mevcut"
|
||||
"{{name}} cannot be empty": "{{name}} boş olamaz"
|
||||
"{{name}} creation failed": "{{name}} oluşturma başarısız"
|
||||
"{{name}} deletion failed": "{{name}} silme başarısız"
|
||||
"{{name}} is required": "{{name}} gerekli"
|
||||
"{{name}} not found": "{{name}} bulunamadı"
|
||||
"{{name}} save failed": "{{name}} kaydetme başarısız"
|
||||
@@ -75,7 +75,7 @@ func (c *AkismetChecker) Check(p *CheckerParams) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf(respStr)
|
||||
return false, fmt.Errorf("%s", respStr)
|
||||
}
|
||||
|
||||
type AkismetReqParams struct {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package anti_spam
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -87,6 +88,9 @@ func (c *KeywordsChecker) Check(p *CheckerParams) (bool, error) {
|
||||
func (c *KeywordsChecker) loadKeywords() error {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
if c.conf.FileSep == "" {
|
||||
return errors.New("keywords file separator cannot be empty")
|
||||
}
|
||||
|
||||
// 已加载过无需再次加载
|
||||
if c.keywords != nil {
|
||||
|
||||
@@ -108,9 +108,23 @@ func TestNewKeywordsChecker(t *testing.T) {
|
||||
assert.False(t, ok)
|
||||
})
|
||||
|
||||
t.Run("ErrorEmptySeparator", func(t *testing.T) {
|
||||
checker := NewKeywordsChecker(&KeywordsCheckerConf{
|
||||
Files: []string{kwFile1},
|
||||
FileSep: "",
|
||||
Mode: KwCheckerModeBlock,
|
||||
})
|
||||
ok, err := checker.Check(&CheckerParams{
|
||||
Content: "关键词A",
|
||||
})
|
||||
assert.ErrorContains(t, err, "separator cannot be empty")
|
||||
assert.False(t, ok)
|
||||
})
|
||||
|
||||
t.Run("ErrorUnknownMode", func(t *testing.T) {
|
||||
checker := NewKeywordsChecker(&KeywordsCheckerConf{
|
||||
Mode: 999,
|
||||
FileSep: "\n",
|
||||
Mode: 999,
|
||||
})
|
||||
ok, err := checker.Check(&CheckerParams{})
|
||||
assert.ErrorContains(t, err, "unknown mode")
|
||||
|
||||
@@ -23,7 +23,7 @@ func RunImportArtrans(dao *dao.Dao, params *ImportParams, outputFunc ...func(str
|
||||
// Read JSON
|
||||
if params.JsonData == "" {
|
||||
if params.JsonFile == "" {
|
||||
err := fmt.Errorf(i18n.T("{{name}} is required", map[string]any{"name": "json_file:<JSON file path>"}))
|
||||
err := fmt.Errorf("%s", i18n.T("{{name}} is required", map[string]any{"name": "json_file:<JSON file path>"}))
|
||||
console.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -44,11 +44,11 @@ func importArtrans(tx *gorm.DB, params *ImportParams, comments []*entity.Artran)
|
||||
isTrue := func(val string) bool { return val == "true" || val == "1" }
|
||||
|
||||
if len(comments) == 0 {
|
||||
return fmt.Errorf(i18n.T("No comment"))
|
||||
return fmt.Errorf("%s", i18n.T("No comment"))
|
||||
}
|
||||
|
||||
if params.TargetSiteURL != "" && !utils.ValidateURL(params.TargetSiteURL) {
|
||||
return fmt.Errorf(i18n.T("Invalid {{name}}", map[string]interface{}{"name": i18n.T("Target Site") + " " + "URL"}))
|
||||
return fmt.Errorf("%s", i18n.T("Invalid {{name}}", map[string]interface{}{"name": i18n.T("Target Site") + " " + "URL"}))
|
||||
}
|
||||
|
||||
console.Println()
|
||||
|
||||
@@ -13,12 +13,12 @@ import (
|
||||
|
||||
func readJsonFile(filename string) (string, error) {
|
||||
if _, err := os.Stat(filename); errors.Is(err, os.ErrNotExist) {
|
||||
return "", fmt.Errorf(i18n.T("{{name}} not found", map[string]any{"name": i18n.T("File")}))
|
||||
return "", fmt.Errorf("%s", i18n.T("{{name}} not found", map[string]any{"name": i18n.T("File")}))
|
||||
}
|
||||
|
||||
buf, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("file open failed" + ": " + err.Error())
|
||||
return "", fmt.Errorf("file open failed: %w", err)
|
||||
}
|
||||
|
||||
return string(buf), nil
|
||||
|
||||
Vendored
+1
-1
@@ -30,7 +30,7 @@ func QueryDBWithCache[T any](c *Cache, name string, queryDB func() (T, error)) (
|
||||
|
||||
// Store cache
|
||||
if err := c.StoreCache(val, name); err != nil {
|
||||
return nil, err
|
||||
log.Warn("[Cache] failed to store database query result: ", err)
|
||||
}
|
||||
|
||||
return val, nil
|
||||
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
lib_cache "github.com/eko/gocache/lib/v4/cache"
|
||||
"github.com/eko/gocache/lib/v4/marshaler"
|
||||
"github.com/eko/gocache/lib/v4/store"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestQueryDBWithCacheReturnsDatabaseValueWhenStoreFails(t *testing.T) {
|
||||
storeErr := errors.New("cache store unavailable")
|
||||
cacheStore := &failureStore{
|
||||
getErr: errors.New("cache miss"),
|
||||
setErr: storeErr,
|
||||
}
|
||||
cacheInstance := newCacheWithStore(cacheStore)
|
||||
defer cacheInstance.Close()
|
||||
|
||||
type user struct {
|
||||
Name string
|
||||
Email string
|
||||
}
|
||||
want := user{Name: "admin", Email: "admin@example.com"}
|
||||
|
||||
got, err := QueryDBWithCache(cacheInstance, t.Name(), func() (user, error) {
|
||||
return want, nil
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, want, got)
|
||||
assert.Equal(t, int32(1), cacheStore.setCalls.Load())
|
||||
}
|
||||
|
||||
func TestQueryDBWithCacheReturnsDatabaseError(t *testing.T) {
|
||||
dbErr := errors.New("database unavailable")
|
||||
cacheStore := &failureStore{getErr: errors.New("cache miss")}
|
||||
cacheInstance := newCacheWithStore(cacheStore)
|
||||
defer cacheInstance.Close()
|
||||
|
||||
got, err := QueryDBWithCache(cacheInstance, t.Name(), func() (string, error) {
|
||||
return "", dbErr
|
||||
})
|
||||
|
||||
require.ErrorIs(t, err, dbErr)
|
||||
assert.Empty(t, got)
|
||||
assert.Equal(t, int32(0), cacheStore.setCalls.Load())
|
||||
}
|
||||
|
||||
func newCacheWithStore(cacheStore store.StoreInterface) *Cache {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
instance := lib_cache.New[any](cacheStore)
|
||||
|
||||
return &Cache{
|
||||
ttl: time.Minute,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
instance: instance,
|
||||
marshal: marshaler.New(instance),
|
||||
}
|
||||
}
|
||||
|
||||
type failureStore struct {
|
||||
getErr error
|
||||
setErr error
|
||||
setCalls atomic.Int32
|
||||
}
|
||||
|
||||
func (s *failureStore) Get(context.Context, any) (any, error) {
|
||||
return nil, s.getErr
|
||||
}
|
||||
|
||||
func (s *failureStore) GetWithTTL(context.Context, any) (any, time.Duration, error) {
|
||||
return nil, 0, s.getErr
|
||||
}
|
||||
|
||||
func (s *failureStore) Set(context.Context, any, any, ...store.Option) error {
|
||||
s.setCalls.Add(1)
|
||||
return s.setErr
|
||||
}
|
||||
|
||||
func (*failureStore) Delete(context.Context, any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*failureStore) Invalidate(context.Context, ...store.InvalidateOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*failureStore) Clear(context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*failureStore) GetType() string {
|
||||
return "failure"
|
||||
}
|
||||
@@ -42,7 +42,7 @@ func GreenText(conf GreenTextConf) (bool, error) {
|
||||
}
|
||||
|
||||
if data.Code != 200 {
|
||||
return false, fmt.Errorf(data.Msg)
|
||||
return false, fmt.Errorf("%s", data.Msg)
|
||||
}
|
||||
if len(data.Data) < 1 || len(data.Data[0].Results) < 1 {
|
||||
return false, fmt.Errorf("not expected response: %s", string(body))
|
||||
|
||||
+600
-2
@@ -5,7 +5,605 @@ package config
|
||||
import "github.com/artalkjs/artalk/v2/internal/config/meta"
|
||||
|
||||
// Cache result of `meta.GetEnvPathMap(config.Config{}, config.Template("en"))`
|
||||
var EnvPathMapCache = map[string]string{"ATK_ADMIN_NOTIFY_BARK_ENABLED":"admin_notify.bark.enabled", "ATK_ADMIN_NOTIFY_BARK_SERVER":"admin_notify.bark.server", "ATK_ADMIN_NOTIFY_DING_TALK_ENABLED":"admin_notify.ding_talk.enabled", "ATK_ADMIN_NOTIFY_DING_TALK_SECRET":"admin_notify.ding_talk.secret", "ATK_ADMIN_NOTIFY_DING_TALK_TOKEN":"admin_notify.ding_talk.token", "ATK_ADMIN_NOTIFY_EMAIL":"admin_notify.email", "ATK_ADMIN_NOTIFY_EMAIL_ENABLED":"admin_notify.email.enabled", "ATK_ADMIN_NOTIFY_EMAIL_MAIL_SUBJECT":"admin_notify.email.mail_subject", "ATK_ADMIN_NOTIFY_EMAIL_MAIL_TPL":"admin_notify.email.mail_tpl", "ATK_ADMIN_NOTIFY_LARK_ENABLED":"admin_notify.lark.enabled", "ATK_ADMIN_NOTIFY_LARK_MSG_TYPE":"admin_notify.lark.msg_type", "ATK_ADMIN_NOTIFY_LARK_WEBHOOK_URL":"admin_notify.lark.webhook_url", "ATK_ADMIN_NOTIFY_LINE_CHANNEL_ACCESS_TOKEN":"admin_notify.line.channel_access_token", "ATK_ADMIN_NOTIFY_LINE_CHANNEL_SECRET":"admin_notify.line.channel_secret", "ATK_ADMIN_NOTIFY_LINE_ENABLED":"admin_notify.line.enabled", "ATK_ADMIN_NOTIFY_LINE_RECEIVERS":"admin_notify.line.receivers", "ATK_ADMIN_NOTIFY_LINE_RECEIVERS_$$":"admin_notify.line.receivers.$$", "ATK_ADMIN_NOTIFY_NOISE_MODE":"admin_notify.noise_mode", "ATK_ADMIN_NOTIFY_NOTIFY_PENDING":"admin_notify.notify_pending", "ATK_ADMIN_NOTIFY_NOTIFY_SUBJECT":"admin_notify.notify_subject", "ATK_ADMIN_NOTIFY_NOTIFY_TPL":"admin_notify.notify_tpl", "ATK_ADMIN_NOTIFY_SLACK_ENABLED":"admin_notify.slack.enabled", "ATK_ADMIN_NOTIFY_SLACK_OAUTH_TOKEN":"admin_notify.slack.oauth_token", "ATK_ADMIN_NOTIFY_SLACK_RECEIVERS":"admin_notify.slack.receivers", "ATK_ADMIN_NOTIFY_SLACK_RECEIVERS_$$":"admin_notify.slack.receivers.$$", "ATK_ADMIN_NOTIFY_TELEGRAM_API_TOKEN":"admin_notify.telegram.api_token", "ATK_ADMIN_NOTIFY_TELEGRAM_ENABLED":"admin_notify.telegram.enabled", "ATK_ADMIN_NOTIFY_TELEGRAM_RECEIVERS":"admin_notify.telegram.receivers", "ATK_ADMIN_NOTIFY_TELEGRAM_RECEIVERS_$$":"admin_notify.telegram.receivers.$$", "ATK_ADMIN_NOTIFY_WEBHOOK_ENABLED":"admin_notify.webhook.enabled", "ATK_ADMIN_NOTIFY_WEBHOOK_URL":"admin_notify.webhook.url", "ATK_ADMIN_USERS_$$_BADGE_COLOR":"admin_users.$$.badge_color", "ATK_ADMIN_USERS_$$_BADGE_NAME":"admin_users.$$.badge_name", "ATK_ADMIN_USERS_$$_EMAIL":"admin_users.$$.email", "ATK_ADMIN_USERS_$$_LINK":"admin_users.$$.link", "ATK_ADMIN_USERS_$$_NAME":"admin_users.$$.name", "ATK_ADMIN_USERS_$$_PASSWORD":"admin_users.$$.password", "ATK_ADMIN_USERS_$$_RECEIVE_EMAIL":"admin_users.$$.receive_email", "ATK_ADMIN__NOTIFY_BARK_ENABLED":"admin_notify.bark.enabled", "ATK_ADMIN__NOTIFY_BARK_SERVER":"admin_notify.bark.server", "ATK_ADMIN__NOTIFY_DING__TALK_ENABLED":"admin_notify.ding_talk.enabled", "ATK_ADMIN__NOTIFY_DING__TALK_SECRET":"admin_notify.ding_talk.secret", "ATK_ADMIN__NOTIFY_DING__TALK_TOKEN":"admin_notify.ding_talk.token", "ATK_ADMIN__NOTIFY_EMAIL":"admin_notify.email", "ATK_ADMIN__NOTIFY_LARK_ENABLED":"admin_notify.lark.enabled", "ATK_ADMIN__NOTIFY_LARK_MSG__TYPE":"admin_notify.lark.msg_type", "ATK_ADMIN__NOTIFY_LARK_WEBHOOK__URL":"admin_notify.lark.webhook_url", "ATK_ADMIN__NOTIFY_LINE_CHANNEL__ACCESS__TOKEN":"admin_notify.line.channel_access_token", "ATK_ADMIN__NOTIFY_LINE_CHANNEL__SECRET":"admin_notify.line.channel_secret", "ATK_ADMIN__NOTIFY_LINE_ENABLED":"admin_notify.line.enabled", "ATK_ADMIN__NOTIFY_LINE_RECEIVERS_$$":"admin_notify.line.receivers.$$", "ATK_ADMIN__NOTIFY_NOISE__MODE":"admin_notify.noise_mode", "ATK_ADMIN__NOTIFY_NOTIFY__PENDING":"admin_notify.notify_pending", "ATK_ADMIN__NOTIFY_NOTIFY__SUBJECT":"admin_notify.notify_subject", "ATK_ADMIN__NOTIFY_NOTIFY__TPL":"admin_notify.notify_tpl", "ATK_ADMIN__NOTIFY_SLACK_ENABLED":"admin_notify.slack.enabled", "ATK_ADMIN__NOTIFY_SLACK_OAUTH__TOKEN":"admin_notify.slack.oauth_token", "ATK_ADMIN__NOTIFY_SLACK_RECEIVERS_$$":"admin_notify.slack.receivers.$$", "ATK_ADMIN__NOTIFY_TELEGRAM_API__TOKEN":"admin_notify.telegram.api_token", "ATK_ADMIN__NOTIFY_TELEGRAM_ENABLED":"admin_notify.telegram.enabled", "ATK_ADMIN__NOTIFY_TELEGRAM_RECEIVERS_$$":"admin_notify.telegram.receivers.$$", "ATK_ADMIN__NOTIFY_WEBHOOK_ENABLED":"admin_notify.webhook.enabled", "ATK_ADMIN__NOTIFY_WEBHOOK_URL":"admin_notify.webhook.url", "ATK_ADMIN__USERS_$$_BADGE__COLOR":"admin_users.$$.badge_color", "ATK_ADMIN__USERS_$$_BADGE__NAME":"admin_users.$$.badge_name", "ATK_ADMIN__USERS_$$_EMAIL":"admin_users.$$.email", "ATK_ADMIN__USERS_$$_LINK":"admin_users.$$.link", "ATK_ADMIN__USERS_$$_NAME":"admin_users.$$.name", "ATK_ADMIN__USERS_$$_PASSWORD":"admin_users.$$.password", "ATK_ADMIN__USERS_$$_RECEIVE__EMAIL":"admin_users.$$.receive_email", "ATK_APP_KEY":"app_key", "ATK_APP__KEY":"app_key", "ATK_AUTH_ANONYMOUS":"auth.anonymous", "ATK_AUTH_APPLE_CLIENT_ID":"auth.apple.client_id", "ATK_AUTH_APPLE_CLIENT_SECRET":"auth.apple.client_secret", "ATK_AUTH_APPLE_CLIENT__ID":"auth.apple.client_id", "ATK_AUTH_APPLE_CLIENT__SECRET":"auth.apple.client_secret", "ATK_AUTH_APPLE_ENABLED":"auth.apple.enabled", "ATK_AUTH_AUTH0_CLIENT_ID":"auth.Line truncated
|
||||
var EnvPathMapCache = map[string]string{
|
||||
"ATK_ADMIN_NOTIFY_BARK_ENABLED": "admin_notify.bark.enabled",
|
||||
"ATK_ADMIN_NOTIFY_BARK_SERVER": "admin_notify.bark.server",
|
||||
"ATK_ADMIN_NOTIFY_DING_TALK_ENABLED": "admin_notify.ding_talk.enabled",
|
||||
"ATK_ADMIN_NOTIFY_DING_TALK_SECRET": "admin_notify.ding_talk.secret",
|
||||
"ATK_ADMIN_NOTIFY_DING_TALK_TOKEN": "admin_notify.ding_talk.token",
|
||||
"ATK_ADMIN_NOTIFY_EMAIL": "admin_notify.email",
|
||||
"ATK_ADMIN_NOTIFY_EMAIL_ENABLED": "admin_notify.email.enabled",
|
||||
"ATK_ADMIN_NOTIFY_EMAIL_MAIL_SUBJECT": "admin_notify.email.mail_subject",
|
||||
"ATK_ADMIN_NOTIFY_EMAIL_MAIL_TPL": "admin_notify.email.mail_tpl",
|
||||
"ATK_ADMIN_NOTIFY_LARK_ENABLED": "admin_notify.lark.enabled",
|
||||
"ATK_ADMIN_NOTIFY_LARK_MSG_TYPE": "admin_notify.lark.msg_type",
|
||||
"ATK_ADMIN_NOTIFY_LARK_WEBHOOK_URL": "admin_notify.lark.webhook_url",
|
||||
"ATK_ADMIN_NOTIFY_LINE_CHANNEL_ACCESS_TOKEN": "admin_notify.line.channel_access_token",
|
||||
"ATK_ADMIN_NOTIFY_LINE_CHANNEL_SECRET": "admin_notify.line.channel_secret",
|
||||
"ATK_ADMIN_NOTIFY_LINE_ENABLED": "admin_notify.line.enabled",
|
||||
"ATK_ADMIN_NOTIFY_LINE_RECEIVERS": "admin_notify.line.receivers",
|
||||
"ATK_ADMIN_NOTIFY_LINE_RECEIVERS_$$": "admin_notify.line.receivers.$$",
|
||||
"ATK_ADMIN_NOTIFY_NOISE_MODE": "admin_notify.noise_mode",
|
||||
"ATK_ADMIN_NOTIFY_NOTIFY_PENDING": "admin_notify.notify_pending",
|
||||
"ATK_ADMIN_NOTIFY_NOTIFY_SUBJECT": "admin_notify.notify_subject",
|
||||
"ATK_ADMIN_NOTIFY_NOTIFY_TPL": "admin_notify.notify_tpl",
|
||||
"ATK_ADMIN_NOTIFY_SLACK_ENABLED": "admin_notify.slack.enabled",
|
||||
"ATK_ADMIN_NOTIFY_SLACK_OAUTH_TOKEN": "admin_notify.slack.oauth_token",
|
||||
"ATK_ADMIN_NOTIFY_SLACK_RECEIVERS": "admin_notify.slack.receivers",
|
||||
"ATK_ADMIN_NOTIFY_SLACK_RECEIVERS_$$": "admin_notify.slack.receivers.$$",
|
||||
"ATK_ADMIN_NOTIFY_TELEGRAM_API_TOKEN": "admin_notify.telegram.api_token",
|
||||
"ATK_ADMIN_NOTIFY_TELEGRAM_ENABLED": "admin_notify.telegram.enabled",
|
||||
"ATK_ADMIN_NOTIFY_TELEGRAM_RECEIVERS": "admin_notify.telegram.receivers",
|
||||
"ATK_ADMIN_NOTIFY_TELEGRAM_RECEIVERS_$$": "admin_notify.telegram.receivers.$$",
|
||||
"ATK_ADMIN_NOTIFY_WEBHOOK_ENABLED": "admin_notify.webhook.enabled",
|
||||
"ATK_ADMIN_NOTIFY_WEBHOOK_URL": "admin_notify.webhook.url",
|
||||
"ATK_ADMIN_USERS_$$_BADGE_COLOR": "admin_users.$$.badge_color",
|
||||
"ATK_ADMIN_USERS_$$_BADGE_NAME": "admin_users.$$.badge_name",
|
||||
"ATK_ADMIN_USERS_$$_EMAIL": "admin_users.$$.email",
|
||||
"ATK_ADMIN_USERS_$$_LINK": "admin_users.$$.link",
|
||||
"ATK_ADMIN_USERS_$$_NAME": "admin_users.$$.name",
|
||||
"ATK_ADMIN_USERS_$$_PASSWORD": "admin_users.$$.password",
|
||||
"ATK_ADMIN_USERS_$$_RECEIVE_EMAIL": "admin_users.$$.receive_email",
|
||||
"ATK_ADMIN__NOTIFY_BARK_ENABLED": "admin_notify.bark.enabled",
|
||||
"ATK_ADMIN__NOTIFY_BARK_SERVER": "admin_notify.bark.server",
|
||||
"ATK_ADMIN__NOTIFY_DING__TALK_ENABLED": "admin_notify.ding_talk.enabled",
|
||||
"ATK_ADMIN__NOTIFY_DING__TALK_SECRET": "admin_notify.ding_talk.secret",
|
||||
"ATK_ADMIN__NOTIFY_DING__TALK_TOKEN": "admin_notify.ding_talk.token",
|
||||
"ATK_ADMIN__NOTIFY_EMAIL": "admin_notify.email",
|
||||
"ATK_ADMIN__NOTIFY_LARK_ENABLED": "admin_notify.lark.enabled",
|
||||
"ATK_ADMIN__NOTIFY_LARK_MSG__TYPE": "admin_notify.lark.msg_type",
|
||||
"ATK_ADMIN__NOTIFY_LARK_WEBHOOK__URL": "admin_notify.lark.webhook_url",
|
||||
"ATK_ADMIN__NOTIFY_LINE_CHANNEL__ACCESS__TOKEN": "admin_notify.line.channel_access_token",
|
||||
"ATK_ADMIN__NOTIFY_LINE_CHANNEL__SECRET": "admin_notify.line.channel_secret",
|
||||
"ATK_ADMIN__NOTIFY_LINE_ENABLED": "admin_notify.line.enabled",
|
||||
"ATK_ADMIN__NOTIFY_LINE_RECEIVERS_$$": "admin_notify.line.receivers.$$",
|
||||
"ATK_ADMIN__NOTIFY_NOISE__MODE": "admin_notify.noise_mode",
|
||||
"ATK_ADMIN__NOTIFY_NOTIFY__PENDING": "admin_notify.notify_pending",
|
||||
"ATK_ADMIN__NOTIFY_NOTIFY__SUBJECT": "admin_notify.notify_subject",
|
||||
"ATK_ADMIN__NOTIFY_NOTIFY__TPL": "admin_notify.notify_tpl",
|
||||
"ATK_ADMIN__NOTIFY_SLACK_ENABLED": "admin_notify.slack.enabled",
|
||||
"ATK_ADMIN__NOTIFY_SLACK_OAUTH__TOKEN": "admin_notify.slack.oauth_token",
|
||||
"ATK_ADMIN__NOTIFY_SLACK_RECEIVERS_$$": "admin_notify.slack.receivers.$$",
|
||||
"ATK_ADMIN__NOTIFY_TELEGRAM_API__TOKEN": "admin_notify.telegram.api_token",
|
||||
"ATK_ADMIN__NOTIFY_TELEGRAM_ENABLED": "admin_notify.telegram.enabled",
|
||||
"ATK_ADMIN__NOTIFY_TELEGRAM_RECEIVERS_$$": "admin_notify.telegram.receivers.$$",
|
||||
"ATK_ADMIN__NOTIFY_WEBHOOK_ENABLED": "admin_notify.webhook.enabled",
|
||||
"ATK_ADMIN__NOTIFY_WEBHOOK_URL": "admin_notify.webhook.url",
|
||||
"ATK_ADMIN__USERS_$$_BADGE__COLOR": "admin_users.$$.badge_color",
|
||||
"ATK_ADMIN__USERS_$$_BADGE__NAME": "admin_users.$$.badge_name",
|
||||
"ATK_ADMIN__USERS_$$_EMAIL": "admin_users.$$.email",
|
||||
"ATK_ADMIN__USERS_$$_LINK": "admin_users.$$.link",
|
||||
"ATK_ADMIN__USERS_$$_NAME": "admin_users.$$.name",
|
||||
"ATK_ADMIN__USERS_$$_PASSWORD": "admin_users.$$.password",
|
||||
"ATK_ADMIN__USERS_$$_RECEIVE__EMAIL": "admin_users.$$.receive_email",
|
||||
"ATK_APP_KEY": "app_key",
|
||||
"ATK_APP__KEY": "app_key",
|
||||
"ATK_AUTH_ANONYMOUS": "auth.anonymous",
|
||||
"ATK_AUTH_APPLE_CLIENT_ID": "auth.apple.client_id",
|
||||
"ATK_AUTH_APPLE_CLIENT_SECRET": "auth.apple.client_secret",
|
||||
"ATK_AUTH_APPLE_CLIENT__ID": "auth.apple.client_id",
|
||||
"ATK_AUTH_APPLE_CLIENT__SECRET": "auth.apple.client_secret",
|
||||
"ATK_AUTH_APPLE_ENABLED": "auth.apple.enabled",
|
||||
"ATK_AUTH_AUTH0_CLIENT_ID": "auth.auth0.client_id",
|
||||
"ATK_AUTH_AUTH0_CLIENT_SECRET": "auth.auth0.client_secret",
|
||||
"ATK_AUTH_AUTH0_CLIENT__ID": "auth.auth0.client_id",
|
||||
"ATK_AUTH_AUTH0_CLIENT__SECRET": "auth.auth0.client_secret",
|
||||
"ATK_AUTH_AUTH0_DOMAIN": "auth.auth0.domain",
|
||||
"ATK_AUTH_AUTH0_ENABLED": "auth.auth0.enabled",
|
||||
"ATK_AUTH_CALLBACK": "auth.callback",
|
||||
"ATK_AUTH_DISCORD_CLIENT_ID": "auth.discord.client_id",
|
||||
"ATK_AUTH_DISCORD_CLIENT_SECRET": "auth.discord.client_secret",
|
||||
"ATK_AUTH_DISCORD_CLIENT__ID": "auth.discord.client_id",
|
||||
"ATK_AUTH_DISCORD_CLIENT__SECRET": "auth.discord.client_secret",
|
||||
"ATK_AUTH_DISCORD_ENABLED": "auth.discord.enabled",
|
||||
"ATK_AUTH_EMAIL_ENABLED": "auth.email.enabled",
|
||||
"ATK_AUTH_EMAIL_VERIFY_SUBJECT": "auth.email.verify_subject",
|
||||
"ATK_AUTH_EMAIL_VERIFY_TPL": "auth.email.verify_tpl",
|
||||
"ATK_AUTH_EMAIL_VERIFY__SUBJECT": "auth.email.verify_subject",
|
||||
"ATK_AUTH_EMAIL_VERIFY__TPL": "auth.email.verify_tpl",
|
||||
"ATK_AUTH_ENABLED": "auth.enabled",
|
||||
"ATK_AUTH_FACEBOOK_CLIENT_ID": "auth.facebook.client_id",
|
||||
"ATK_AUTH_FACEBOOK_CLIENT_SECRET": "auth.facebook.client_secret",
|
||||
"ATK_AUTH_FACEBOOK_CLIENT__ID": "auth.facebook.client_id",
|
||||
"ATK_AUTH_FACEBOOK_CLIENT__SECRET": "auth.facebook.client_secret",
|
||||
"ATK_AUTH_FACEBOOK_ENABLED": "auth.facebook.enabled",
|
||||
"ATK_AUTH_GITEA_CLIENT_ID": "auth.gitea.client_id",
|
||||
"ATK_AUTH_GITEA_CLIENT_SECRET": "auth.gitea.client_secret",
|
||||
"ATK_AUTH_GITEA_CLIENT__ID": "auth.gitea.client_id",
|
||||
"ATK_AUTH_GITEA_CLIENT__SECRET": "auth.gitea.client_secret",
|
||||
"ATK_AUTH_GITEA_ENABLED": "auth.gitea.enabled",
|
||||
"ATK_AUTH_GITHUB_CLIENT_ID": "auth.github.client_id",
|
||||
"ATK_AUTH_GITHUB_CLIENT_SECRET": "auth.github.client_secret",
|
||||
"ATK_AUTH_GITHUB_CLIENT__ID": "auth.github.client_id",
|
||||
"ATK_AUTH_GITHUB_CLIENT__SECRET": "auth.github.client_secret",
|
||||
"ATK_AUTH_GITHUB_ENABLED": "auth.github.enabled",
|
||||
"ATK_AUTH_GITLAB_CLIENT_ID": "auth.gitlab.client_id",
|
||||
"ATK_AUTH_GITLAB_CLIENT_SECRET": "auth.gitlab.client_secret",
|
||||
"ATK_AUTH_GITLAB_CLIENT__ID": "auth.gitlab.client_id",
|
||||
"ATK_AUTH_GITLAB_CLIENT__SECRET": "auth.gitlab.client_secret",
|
||||
"ATK_AUTH_GITLAB_ENABLED": "auth.gitlab.enabled",
|
||||
"ATK_AUTH_GOOGLE_CLIENT_ID": "auth.google.client_id",
|
||||
"ATK_AUTH_GOOGLE_CLIENT_SECRET": "auth.google.client_secret",
|
||||
"ATK_AUTH_GOOGLE_CLIENT__ID": "auth.google.client_id",
|
||||
"ATK_AUTH_GOOGLE_CLIENT__SECRET": "auth.google.client_secret",
|
||||
"ATK_AUTH_GOOGLE_ENABLED": "auth.google.enabled",
|
||||
"ATK_AUTH_LINE_CLIENT_ID": "auth.line.client_id",
|
||||
"ATK_AUTH_LINE_CLIENT_SECRET": "auth.line.client_secret",
|
||||
"ATK_AUTH_LINE_CLIENT__ID": "auth.line.client_id",
|
||||
"ATK_AUTH_LINE_CLIENT__SECRET": "auth.line.client_secret",
|
||||
"ATK_AUTH_LINE_ENABLED": "auth.line.enabled",
|
||||
"ATK_AUTH_MASTODON_CLIENT_ID": "auth.mastodon.client_id",
|
||||
"ATK_AUTH_MASTODON_CLIENT_SECRET": "auth.mastodon.client_secret",
|
||||
"ATK_AUTH_MASTODON_CLIENT__ID": "auth.mastodon.client_id",
|
||||
"ATK_AUTH_MASTODON_CLIENT__SECRET": "auth.mastodon.client_secret",
|
||||
"ATK_AUTH_MASTODON_ENABLED": "auth.mastodon.enabled",
|
||||
"ATK_AUTH_MICROSOFT_CLIENT_ID": "auth.microsoft.client_id",
|
||||
"ATK_AUTH_MICROSOFT_CLIENT_SECRET": "auth.microsoft.client_secret",
|
||||
"ATK_AUTH_MICROSOFT_CLIENT__ID": "auth.microsoft.client_id",
|
||||
"ATK_AUTH_MICROSOFT_CLIENT__SECRET": "auth.microsoft.client_secret",
|
||||
"ATK_AUTH_MICROSOFT_ENABLED": "auth.microsoft.enabled",
|
||||
"ATK_AUTH_PATREON_CLIENT_ID": "auth.patreon.client_id",
|
||||
"ATK_AUTH_PATREON_CLIENT_SECRET": "auth.patreon.client_secret",
|
||||
"ATK_AUTH_PATREON_CLIENT__ID": "auth.patreon.client_id",
|
||||
"ATK_AUTH_PATREON_CLIENT__SECRET": "auth.patreon.client_secret",
|
||||
"ATK_AUTH_PATREON_ENABLED": "auth.patreon.enabled",
|
||||
"ATK_AUTH_SLACK_CLIENT_ID": "auth.slack.client_id",
|
||||
"ATK_AUTH_SLACK_CLIENT_SECRET": "auth.slack.client_secret",
|
||||
"ATK_AUTH_SLACK_CLIENT__ID": "auth.slack.client_id",
|
||||
"ATK_AUTH_SLACK_CLIENT__SECRET": "auth.slack.client_secret",
|
||||
"ATK_AUTH_SLACK_ENABLED": "auth.slack.enabled",
|
||||
"ATK_AUTH_SSO_ENABLED": "auth.sso.enabled",
|
||||
"ATK_AUTH_SSO_ISSUER": "auth.sso.issuer",
|
||||
"ATK_AUTH_STEAM_API_KEY": "auth.steam.api_key",
|
||||
"ATK_AUTH_STEAM_API__KEY": "auth.steam.api_key",
|
||||
"ATK_AUTH_STEAM_ENABLED": "auth.steam.enabled",
|
||||
"ATK_AUTH_TIKTOK_CLIENT_ID": "auth.tiktok.client_id",
|
||||
"ATK_AUTH_TIKTOK_CLIENT_SECRET": "auth.tiktok.client_secret",
|
||||
"ATK_AUTH_TIKTOK_CLIENT__ID": "auth.tiktok.client_id",
|
||||
"ATK_AUTH_TIKTOK_CLIENT__SECRET": "auth.tiktok.client_secret",
|
||||
"ATK_AUTH_TIKTOK_ENABLED": "auth.tiktok.enabled",
|
||||
"ATK_AUTH_TWITTER_CLIENT_ID": "auth.twitter.client_id",
|
||||
"ATK_AUTH_TWITTER_CLIENT_SECRET": "auth.twitter.client_secret",
|
||||
"ATK_AUTH_TWITTER_CLIENT__ID": "auth.twitter.client_id",
|
||||
"ATK_AUTH_TWITTER_CLIENT__SECRET": "auth.twitter.client_secret",
|
||||
"ATK_AUTH_TWITTER_ENABLED": "auth.twitter.enabled",
|
||||
"ATK_AUTH_WECHAT_CLIENT_ID": "auth.wechat.client_id",
|
||||
"ATK_AUTH_WECHAT_CLIENT_SECRET": "auth.wechat.client_secret",
|
||||
"ATK_AUTH_WECHAT_CLIENT__ID": "auth.wechat.client_id",
|
||||
"ATK_AUTH_WECHAT_CLIENT__SECRET": "auth.wechat.client_secret",
|
||||
"ATK_AUTH_WECHAT_ENABLED": "auth.wechat.enabled",
|
||||
"ATK_CACHE_ENABLED": "cache.enabled",
|
||||
"ATK_CACHE_EXPIRES": "cache.expires",
|
||||
"ATK_CACHE_REDIS_DB": "cache.redis.db",
|
||||
"ATK_CACHE_REDIS_NETWORK": "cache.redis.network",
|
||||
"ATK_CACHE_REDIS_PASSWORD": "cache.redis.password",
|
||||
"ATK_CACHE_REDIS_USERNAME": "cache.redis.username",
|
||||
"ATK_CACHE_SERVER": "cache.server",
|
||||
"ATK_CACHE_TYPE": "cache.type",
|
||||
"ATK_CACHE_WARM_UP": "cache.warm_up",
|
||||
"ATK_CACHE_WARM__UP": "cache.warm_up",
|
||||
"ATK_CAPTCHA_ACTION_LIMIT": "captcha.action_limit",
|
||||
"ATK_CAPTCHA_ACTION_RESET": "captcha.action_reset",
|
||||
"ATK_CAPTCHA_ACTION__LIMIT": "captcha.action_limit",
|
||||
"ATK_CAPTCHA_ACTION__RESET": "captcha.action_reset",
|
||||
"ATK_CAPTCHA_ALWAYS": "captcha.always",
|
||||
"ATK_CAPTCHA_CAPTCHA_TYPE": "captcha.captcha_type",
|
||||
"ATK_CAPTCHA_CAPTCHA__TYPE": "captcha.captcha_type",
|
||||
"ATK_CAPTCHA_ENABLED": "captcha.enabled",
|
||||
"ATK_CAPTCHA_GEETEST_CAPTCHA_ID": "captcha.geetest.captcha_id",
|
||||
"ATK_CAPTCHA_GEETEST_CAPTCHA_KEY": "captcha.geetest.captcha_key",
|
||||
"ATK_CAPTCHA_GEETEST_CAPTCHA__ID": "captcha.geetest.captcha_id",
|
||||
"ATK_CAPTCHA_GEETEST_CAPTCHA__KEY": "captcha.geetest.captcha_key",
|
||||
"ATK_CAPTCHA_HCAPTCHA_SECRET_KEY": "captcha.hcaptcha.secret_key",
|
||||
"ATK_CAPTCHA_HCAPTCHA_SECRET__KEY": "captcha.hcaptcha.secret_key",
|
||||
"ATK_CAPTCHA_HCAPTCHA_SITE_KEY": "captcha.hcaptcha.site_key",
|
||||
"ATK_CAPTCHA_HCAPTCHA_SITE__KEY": "captcha.hcaptcha.site_key",
|
||||
"ATK_CAPTCHA_RECAPTCHA_SECRET_KEY": "captcha.recaptcha.secret_key",
|
||||
"ATK_CAPTCHA_RECAPTCHA_SECRET__KEY": "captcha.recaptcha.secret_key",
|
||||
"ATK_CAPTCHA_RECAPTCHA_SITE_KEY": "captcha.recaptcha.site_key",
|
||||
"ATK_CAPTCHA_RECAPTCHA_SITE__KEY": "captcha.recaptcha.site_key",
|
||||
"ATK_CAPTCHA_TURNSTILE_SECRET_KEY": "captcha.turnstile.secret_key",
|
||||
"ATK_CAPTCHA_TURNSTILE_SECRET__KEY": "captcha.turnstile.secret_key",
|
||||
"ATK_CAPTCHA_TURNSTILE_SITE_KEY": "captcha.turnstile.site_key",
|
||||
"ATK_CAPTCHA_TURNSTILE_SITE__KEY": "captcha.turnstile.site_key",
|
||||
"ATK_DB_CHARSET": "db.charset",
|
||||
"ATK_DB_CLIENT_CERT_PATH": "db.client_cert_path",
|
||||
"ATK_DB_CLIENT_KEY_PATH": "db.client_key_path",
|
||||
"ATK_DB_CLIENT__CERT__PATH": "db.client_cert_path",
|
||||
"ATK_DB_CLIENT__KEY__PATH": "db.client_key_path",
|
||||
"ATK_DB_DSN": "db.dsn",
|
||||
"ATK_DB_FILE": "db.file",
|
||||
"ATK_DB_HOST": "db.host",
|
||||
"ATK_DB_NAME": "db.name",
|
||||
"ATK_DB_PASSWORD": "db.password",
|
||||
"ATK_DB_PORT": "db.port",
|
||||
"ATK_DB_PREPARE_STMT": "db.prepare_stmt",
|
||||
"ATK_DB_PREPARE__STMT": "db.prepare_stmt",
|
||||
"ATK_DB_SERVER_CA_PATH": "db.server_ca_path",
|
||||
"ATK_DB_SERVER__CA__PATH": "db.server_ca_path",
|
||||
"ATK_DB_SSL": "db.ssl",
|
||||
"ATK_DB_TABLE_PREFIX": "db.table_prefix",
|
||||
"ATK_DB_TABLE__PREFIX": "db.table_prefix",
|
||||
"ATK_DB_TYPE": "db.type",
|
||||
"ATK_DB_USER": "db.user",
|
||||
"ATK_DEBUG": "debug",
|
||||
"ATK_EMAIL_ALI_DM_ACCESS_KEY_ID": "email.ali_dm.access_key_id",
|
||||
"ATK_EMAIL_ALI_DM_ACCESS_KEY_SECRET": "email.ali_dm.access_key_secret",
|
||||
"ATK_EMAIL_ALI_DM_ACCOUNT_NAME": "email.ali_dm.account_name",
|
||||
"ATK_EMAIL_ALI_DM_REGION": "email.ali_dm.region",
|
||||
"ATK_EMAIL_ALI__DM_ACCESS__KEY__ID": "email.ali_dm.access_key_id",
|
||||
"ATK_EMAIL_ALI__DM_ACCESS__KEY__SECRET": "email.ali_dm.access_key_secret",
|
||||
"ATK_EMAIL_ALI__DM_ACCOUNT__NAME": "email.ali_dm.account_name",
|
||||
"ATK_EMAIL_ALI__DM_REGION": "email.ali_dm.region",
|
||||
"ATK_EMAIL_ENABLED": "email.enabled",
|
||||
"ATK_EMAIL_MAIL_SUBJECT": "email.mail_subject",
|
||||
"ATK_EMAIL_MAIL_TPL": "email.mail_tpl",
|
||||
"ATK_EMAIL_MAIL__SUBJECT": "email.mail_subject",
|
||||
"ATK_EMAIL_MAIL__TPL": "email.mail_tpl",
|
||||
"ATK_EMAIL_QUEUE_BUFFER_SIZE": "email.queue.buffer_size",
|
||||
"ATK_EMAIL_QUEUE_BUFFER__SIZE": "email.queue.buffer_size",
|
||||
"ATK_EMAIL_SEND_ADDR": "email.send_addr",
|
||||
"ATK_EMAIL_SEND_NAME": "email.send_name",
|
||||
"ATK_EMAIL_SEND_TYPE": "email.send_type",
|
||||
"ATK_EMAIL_SEND__ADDR": "email.send_addr",
|
||||
"ATK_EMAIL_SEND__NAME": "email.send_name",
|
||||
"ATK_EMAIL_SEND__TYPE": "email.send_type",
|
||||
"ATK_EMAIL_SMTP_FROM": "email.smtp.from",
|
||||
"ATK_EMAIL_SMTP_HOST": "email.smtp.host",
|
||||
"ATK_EMAIL_SMTP_PASSWORD": "email.smtp.password",
|
||||
"ATK_EMAIL_SMTP_PORT": "email.smtp.port",
|
||||
"ATK_EMAIL_SMTP_USERNAME": "email.smtp.username",
|
||||
"ATK_FRONTEND": "frontend",
|
||||
"ATK_FRONTEND_DARKMODE": "frontend.darkMode",
|
||||
"ATK_FRONTEND_EDITORTRAVEL": "frontend.editorTravel",
|
||||
"ATK_FRONTEND_EMOTICONS": "frontend.emoticons",
|
||||
"ATK_FRONTEND_FLATMODE": "frontend.flatMode",
|
||||
"ATK_FRONTEND_GRAVATAR_MIRROR": "frontend.gravatar.mirror",
|
||||
"ATK_FRONTEND_GRAVATAR_PARAMS": "frontend.gravatar.params",
|
||||
"ATK_FRONTEND_HEIGHTLIMIT_CHILDREN": "frontend.heightLimit.children",
|
||||
"ATK_FRONTEND_HEIGHTLIMIT_CONTENT": "frontend.heightLimit.content",
|
||||
"ATK_FRONTEND_HEIGHTLIMIT_SCROLLABLE": "frontend.heightLimit.scrollable",
|
||||
"ATK_FRONTEND_IMGLAZYLOAD": "frontend.imgLazyLoad",
|
||||
"ATK_FRONTEND_LISTSORT": "frontend.listSort",
|
||||
"ATK_FRONTEND_NESTMAX": "frontend.nestMax",
|
||||
"ATK_FRONTEND_NESTSORT": "frontend.nestSort",
|
||||
"ATK_FRONTEND_NOCOMMENT": "frontend.noComment",
|
||||
"ATK_FRONTEND_PAGINATION_AUTOLOAD": "frontend.pagination.autoLoad",
|
||||
"ATK_FRONTEND_PAGINATION_PAGESIZE": "frontend.pagination.pageSize",
|
||||
"ATK_FRONTEND_PAGINATION_READMORE": "frontend.pagination.readMore",
|
||||
"ATK_FRONTEND_PLACEHOLDER": "frontend.placeholder",
|
||||
"ATK_FRONTEND_PLUGINURLS": "frontend.pluginURLs",
|
||||
"ATK_FRONTEND_PREVIEW": "frontend.preview",
|
||||
"ATK_FRONTEND_REQTIMEOUT": "frontend.reqTimeout",
|
||||
"ATK_FRONTEND_SENDBTN": "frontend.sendBtn",
|
||||
"ATK_FRONTEND_UABADGE": "frontend.uaBadge",
|
||||
"ATK_FRONTEND_VERSIONCHECK": "frontend.versionCheck",
|
||||
"ATK_FRONTEND_VOTE": "frontend.vote",
|
||||
"ATK_FRONTEND_VOTEDOWN": "frontend.voteDown",
|
||||
"ATK_HOST": "host",
|
||||
"ATK_HTTP_BODY_LIMIT": "http.body_limit",
|
||||
"ATK_HTTP_BODY__LIMIT": "http.body_limit",
|
||||
"ATK_HTTP_PROXY_HEADER": "http.proxy_header",
|
||||
"ATK_HTTP_PROXY__HEADER": "http.proxy_header",
|
||||
"ATK_IMG_UPLOAD_ENABLED": "img_upload.enabled",
|
||||
"ATK_IMG_UPLOAD_MAX_SIZE": "img_upload.max_size",
|
||||
"ATK_IMG_UPLOAD_PATH": "img_upload.path",
|
||||
"ATK_IMG_UPLOAD_PUBLIC_PATH": "img_upload.public_path",
|
||||
"ATK_IMG_UPLOAD_QUALITY": "img_upload.quality",
|
||||
"ATK_IMG_UPLOAD_UPGIT_DEL_LOCAL": "img_upload.upgit.del_local",
|
||||
"ATK_IMG_UPLOAD_UPGIT_ENABLED": "img_upload.upgit.enabled",
|
||||
"ATK_IMG_UPLOAD_UPGIT_EXEC": "img_upload.upgit.exec",
|
||||
"ATK_IMG__UPLOAD_ENABLED": "img_upload.enabled",
|
||||
"ATK_IMG__UPLOAD_MAX__SIZE": "img_upload.max_size",
|
||||
"ATK_IMG__UPLOAD_PATH": "img_upload.path",
|
||||
"ATK_IMG__UPLOAD_PUBLIC__PATH": "img_upload.public_path",
|
||||
"ATK_IMG__UPLOAD_QUALITY": "img_upload.quality",
|
||||
"ATK_IMG__UPLOAD_UPGIT_DEL__LOCAL": "img_upload.upgit.del_local",
|
||||
"ATK_IMG__UPLOAD_UPGIT_ENABLED": "img_upload.upgit.enabled",
|
||||
"ATK_IMG__UPLOAD_UPGIT_EXEC": "img_upload.upgit.exec",
|
||||
"ATK_IP_REGION_DB_PATH": "ip_region.db_path",
|
||||
"ATK_IP_REGION_ENABLED": "ip_region.enabled",
|
||||
"ATK_IP_REGION_PRECISION": "ip_region.precision",
|
||||
"ATK_IP__REGION_DB__PATH": "ip_region.db_path",
|
||||
"ATK_IP__REGION_ENABLED": "ip_region.enabled",
|
||||
"ATK_IP__REGION_PRECISION": "ip_region.precision",
|
||||
"ATK_LOCALE": "locale",
|
||||
"ATK_LOGIN_TIMEOUT": "login_timeout",
|
||||
"ATK_LOGIN__TIMEOUT": "login_timeout",
|
||||
"ATK_LOG_ENABLED": "log.enabled",
|
||||
"ATK_LOG_FILENAME": "log.filename",
|
||||
"ATK_MODERATOR_AKISMET_KEY": "moderator.akismet_key",
|
||||
"ATK_MODERATOR_AKISMET__KEY": "moderator.akismet_key",
|
||||
"ATK_MODERATOR_ALIYUN_ACCESS_KEY_ID": "moderator.aliyun.access_key_id",
|
||||
"ATK_MODERATOR_ALIYUN_ACCESS_KEY_SECRET": "moderator.aliyun.access_key_secret",
|
||||
"ATK_MODERATOR_ALIYUN_ACCESS__KEY__ID": "moderator.aliyun.access_key_id",
|
||||
"ATK_MODERATOR_ALIYUN_ACCESS__KEY__SECRET": "moderator.aliyun.access_key_secret",
|
||||
"ATK_MODERATOR_ALIYUN_ENABLED": "moderator.aliyun.enabled",
|
||||
"ATK_MODERATOR_ALIYUN_REGION": "moderator.aliyun.region",
|
||||
"ATK_MODERATOR_API_FAIL_BLOCK": "moderator.api_fail_block",
|
||||
"ATK_MODERATOR_API__FAIL__BLOCK": "moderator.api_fail_block",
|
||||
"ATK_MODERATOR_KEYWORDS_ENABLED": "moderator.keywords.enabled",
|
||||
"ATK_MODERATOR_KEYWORDS_FILES": "moderator.keywords.files",
|
||||
"ATK_MODERATOR_KEYWORDS_FILES_$$": "moderator.keywords.files.$$",
|
||||
"ATK_MODERATOR_KEYWORDS_FILE_SEP": "moderator.keywords.file_sep",
|
||||
"ATK_MODERATOR_KEYWORDS_FILE__SEP": "moderator.keywords.file_sep",
|
||||
"ATK_MODERATOR_KEYWORDS_PENDING": "moderator.keywords.pending",
|
||||
"ATK_MODERATOR_KEYWORDS_REPLACE_TO": "moderator.keywords.replace_to",
|
||||
"ATK_MODERATOR_KEYWORDS_REPLACE__TO": "moderator.keywords.replace_to",
|
||||
"ATK_MODERATOR_PENDING_DEFAULT": "moderator.pending_default",
|
||||
"ATK_MODERATOR_PENDING__DEFAULT": "moderator.pending_default",
|
||||
"ATK_MODERATOR_TENCENT_ENABLED": "moderator.tencent.enabled",
|
||||
"ATK_MODERATOR_TENCENT_REGION": "moderator.tencent.region",
|
||||
"ATK_MODERATOR_TENCENT_SECRET_ID": "moderator.tencent.secret_id",
|
||||
"ATK_MODERATOR_TENCENT_SECRET_KEY": "moderator.tencent.secret_key",
|
||||
"ATK_MODERATOR_TENCENT_SECRET__ID": "moderator.tencent.secret_id",
|
||||
"ATK_MODERATOR_TENCENT_SECRET__KEY": "moderator.tencent.secret_key",
|
||||
"ATK_PORT": "port",
|
||||
"ATK_SITE_DEFAULT": "site_default",
|
||||
"ATK_SITE_URL": "site_url",
|
||||
"ATK_SITE__DEFAULT": "site_default",
|
||||
"ATK_SITE__URL": "site_url",
|
||||
"ATK_SSL_CERT_PATH": "ssl.cert_path",
|
||||
"ATK_SSL_CERT__PATH": "ssl.cert_path",
|
||||
"ATK_SSL_ENABLED": "ssl.enabled",
|
||||
"ATK_SSL_KEY_PATH": "ssl.key_path",
|
||||
"ATK_SSL_KEY__PATH": "ssl.key_path",
|
||||
"ATK_TIMEZONE": "timezone",
|
||||
"ATK_TRUSTED_DOMAINS": "trusted_domains",
|
||||
"ATK_TRUSTED_DOMAINS_$$": "trusted_domains.$$",
|
||||
"ATK_TRUSTED__DOMAINS_$$": "trusted_domains.$$",
|
||||
}
|
||||
|
||||
// Cache result of `meta.GetOptionsMetaData(config.Template("en"))`
|
||||
var OptionsMetaCache = []meta.OptionsMeta{meta.OptionsMeta{Title:"Multi-Push", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY", Path:"admin_notify", PathText:"Multi-Push", Default:interface {}(nil), IsRoot:true, HasChild:true, AllowsSet:false, CommentRaw:"Multi-Push"}, meta.OptionsMeta{Title:"Bark", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_BARK", Path:"admin_notify.bark", PathText:"Multi-Push > Bark", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Bark"}, meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_BARK_ENABLED", Path:"admin_notify.bark.enabled", PathText:"Multi-Push > Bark > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"}, meta.OptionsMeta{Title:"Server", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_BARK_SERVER", Path:"admin_notify.bark.server", PathText:"Multi-Push > Bark > Server", Default:"http://day.app/xxxxxxx/", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Server"}, meta.OptionsMeta{Title:"DingTalk", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_DING_TALK", Path:"admin_notify.ding_talk", PathText:"Multi-Push > DingTalk", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"DingTalk"}, meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_DING_TALK_ENABLED", Path:"admin_notify.ding_talk.enabled", PathText:"Multi-Push > DingTalk > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"}, meta.OptionsMeta{Title:"Secret", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_DING_TALK_SECRET", Path:"admin_notify.ding_talk.secret", PathText:"Multi-Push > DingTalk > Secret", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Secret"}, meta.OptionsMeta{Title:"Token", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_DING_TALK_TOKEN", Path:"admin_notify.ding_talk.token", PathText:"Multi-Push > DingTalk > Token", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Token"}, meta.OptionsMeta{Title:"Notify admin", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_EMAIL", Path:"admin_notify.email", PathText:"Multi-Push > Notify admin", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Notify admin"}, meta.OptionsMeta{Title:"Enable", Desc:"can be disabled when using other push methods", Type:"bool", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_EMAIL_ENABLED", Path:"admin_notify.email.enabled", PathText:"Multi-Push > Notify admin > Enable", Default:true, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enable (can be disabled when using other push methods)"}, meta.OptionsMeta{Title:"Email subject", Desc:"email subject sent to admin", Type:"string", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_EMAIL_MAIL_SUBJECT", Path:"admin_notify.email.mail_subject", PathText:"Multi-Push > Notify admin > Email subject", Default:"[{{site_name}}] Post \"{{page_title}}\" has new a comment", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Email subject (email subject sent to admin)"}, meta.OptionsMeta{Title:"Admin email template file", Desc:"set to file path to use custom template", Type:"string", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_EMAIL_MAIL_TPL", Path:"admin_notify.email.mail_tpl", PathText:"Multi-Push > Notify admin > Admin email template file", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Admin email template file (set to file path to use custom template)"}, meta.OptionsMeta{Title:"Lark", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_LARK", Path:"admin_notify.lark", PathText:"Multi-Push > Lark", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Lark"}, meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_LARK_ENABLED", Path:"admin_notify.lark.enabled", PathText:"Multi-Push > Lark > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"}, meta.OptionsMeta{Title:"Message type", Desc:"", Type:"string", Options:[]string{"text", "card"}, Env:"ATK_ADMIN_NOTIFY_LARK_MSG_TYPE", Path:"admin_notify.lark.msg_type", PathText:"Multi-Push > Lark > Message type", Default:"text", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Message type [\"text\", \"card\"]"}, meta.OptionsMeta{Title:"WebhookUrl", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_LARK_WEBHOOK_URL", Path:"admin_notify.lark.webhook_url", PathText:"Multi-Push > Lark > WebhookUrl", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"WebhookUrl"}, meta.OptionsMeta{Title:"LINE", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_LINE", Path:"admin_noLine truncated
|
||||
var OptionsMetaCache = []meta.OptionsMeta{
|
||||
meta.OptionsMeta{Title:"Multi-Push", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY", Path:"admin_notify", PathText:"Multi-Push", Default:interface {}(nil), IsRoot:true, HasChild:true, AllowsSet:false, CommentRaw:"Multi-Push"},
|
||||
meta.OptionsMeta{Title:"Bark", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_BARK", Path:"admin_notify.bark", PathText:"Multi-Push > Bark", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Bark"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_BARK_ENABLED", Path:"admin_notify.bark.enabled", PathText:"Multi-Push > Bark > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"Server", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_BARK_SERVER", Path:"admin_notify.bark.server", PathText:"Multi-Push > Bark > Server", Default:"http://day.app/xxxxxxx/", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Server"},
|
||||
meta.OptionsMeta{Title:"DingTalk", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_DING_TALK", Path:"admin_notify.ding_talk", PathText:"Multi-Push > DingTalk", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"DingTalk"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_DING_TALK_ENABLED", Path:"admin_notify.ding_talk.enabled", PathText:"Multi-Push > DingTalk > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"Secret", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_DING_TALK_SECRET", Path:"admin_notify.ding_talk.secret", PathText:"Multi-Push > DingTalk > Secret", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Secret"},
|
||||
meta.OptionsMeta{Title:"Token", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_DING_TALK_TOKEN", Path:"admin_notify.ding_talk.token", PathText:"Multi-Push > DingTalk > Token", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Token"},
|
||||
meta.OptionsMeta{Title:"Notify admin", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_EMAIL", Path:"admin_notify.email", PathText:"Multi-Push > Notify admin", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Notify admin"},
|
||||
meta.OptionsMeta{Title:"Enable", Desc:"can be disabled when using other push methods", Type:"bool", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_EMAIL_ENABLED", Path:"admin_notify.email.enabled", PathText:"Multi-Push > Notify admin > Enable", Default:true, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enable (can be disabled when using other push methods)"},
|
||||
meta.OptionsMeta{Title:"Email subject", Desc:"email subject sent to admin", Type:"string", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_EMAIL_MAIL_SUBJECT", Path:"admin_notify.email.mail_subject", PathText:"Multi-Push > Notify admin > Email subject", Default:"[{{site_name}}] Post \"{{page_title}}\" has new a comment", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Email subject (email subject sent to admin)"},
|
||||
meta.OptionsMeta{Title:"Admin email template file", Desc:"set to file path to use custom template", Type:"string", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_EMAIL_MAIL_TPL", Path:"admin_notify.email.mail_tpl", PathText:"Multi-Push > Notify admin > Admin email template file", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Admin email template file (set to file path to use custom template)"},
|
||||
meta.OptionsMeta{Title:"Lark", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_LARK", Path:"admin_notify.lark", PathText:"Multi-Push > Lark", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Lark"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_LARK_ENABLED", Path:"admin_notify.lark.enabled", PathText:"Multi-Push > Lark > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"Message type", Desc:"", Type:"string", Options:[]string{"text", "card"}, Env:"ATK_ADMIN_NOTIFY_LARK_MSG_TYPE", Path:"admin_notify.lark.msg_type", PathText:"Multi-Push > Lark > Message type", Default:"text", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Message type [\"text\", \"card\"]"},
|
||||
meta.OptionsMeta{Title:"WebhookUrl", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_LARK_WEBHOOK_URL", Path:"admin_notify.lark.webhook_url", PathText:"Multi-Push > Lark > WebhookUrl", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"WebhookUrl"},
|
||||
meta.OptionsMeta{Title:"LINE", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_LINE", Path:"admin_notify.line", PathText:"Multi-Push > LINE", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"LINE"},
|
||||
meta.OptionsMeta{Title:"ChannelAccessToken", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_LINE_CHANNEL_ACCESS_TOKEN", Path:"admin_notify.line.channel_access_token", PathText:"Multi-Push > LINE > ChannelAccessToken", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ChannelAccessToken"},
|
||||
meta.OptionsMeta{Title:"ChannelSecret", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_LINE_CHANNEL_SECRET", Path:"admin_notify.line.channel_secret", PathText:"Multi-Push > LINE > ChannelSecret", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ChannelSecret"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_LINE_ENABLED", Path:"admin_notify.line.enabled", PathText:"Multi-Push > LINE > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"Receivers", Desc:"", Type:"[]interface {}", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_LINE_RECEIVERS", Path:"admin_notify.line.receivers", PathText:"Multi-Push > LINE > Receivers", Default:[]interface {}{"USER_ID_1", "GROUP_ID_1"}, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Receivers"},
|
||||
meta.OptionsMeta{Title:"Noise mode", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_NOISE_MODE", Path:"admin_notify.noise_mode", PathText:"Multi-Push > Noise mode", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Noise mode -- noise_mode is disabled by default. -- -- When this option is set to `false`, only messages sent to the administrator will be notified, -- -- such as \"user A\" replies to \"user B\", the communication between these two users will not be notified to the administrator. --"},
|
||||
meta.OptionsMeta{Title:"Pending comment still send notification", Desc:"notifications are still sent when comments are intercepted", Type:"bool", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_NOTIFY_PENDING", Path:"admin_notify.notify_pending", PathText:"Multi-Push > Pending comment still send notification", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Pending comment still send notification (notifications are still sent when comments are intercepted)"},
|
||||
meta.OptionsMeta{Title:"Notification template", Desc:"set to file path to use custom template", Type:"string", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_NOTIFY_TPL", Path:"admin_notify.notify_tpl", PathText:"Multi-Push > Notification template", Default:"default", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Notification template (set to file path to use custom template)"},
|
||||
meta.OptionsMeta{Title:"Slack", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_SLACK", Path:"admin_notify.slack", PathText:"Multi-Push > Slack", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Slack"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_SLACK_ENABLED", Path:"admin_notify.slack.enabled", PathText:"Multi-Push > Slack > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"OauthToken", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_SLACK_OAUTH_TOKEN", Path:"admin_notify.slack.oauth_token", PathText:"Multi-Push > Slack > OauthToken", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"OauthToken"},
|
||||
meta.OptionsMeta{Title:"Receivers", Desc:"", Type:"[]interface {}", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_SLACK_RECEIVERS", Path:"admin_notify.slack.receivers", PathText:"Multi-Push > Slack > Receivers", Default:[]interface {}{"CHANNEL_ID"}, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Receivers"},
|
||||
meta.OptionsMeta{Title:"Telegram", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_TELEGRAM", Path:"admin_notify.telegram", PathText:"Multi-Push > Telegram", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Telegram"},
|
||||
meta.OptionsMeta{Title:"ApiToken", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_TELEGRAM_API_TOKEN", Path:"admin_notify.telegram.api_token", PathText:"Multi-Push > Telegram > ApiToken", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ApiToken"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_TELEGRAM_ENABLED", Path:"admin_notify.telegram.enabled", PathText:"Multi-Push > Telegram > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"Receivers", Desc:"", Type:"[]interface {}", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_TELEGRAM_RECEIVERS", Path:"admin_notify.telegram.receivers", PathText:"Multi-Push > Telegram > Receivers", Default:[]interface {}{0x76adf1}, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Receivers"},
|
||||
meta.OptionsMeta{Title:"WebHook", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_WEBHOOK", Path:"admin_notify.webhook", PathText:"Multi-Push > WebHook", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"WebHook"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_WEBHOOK_ENABLED", Path:"admin_notify.webhook.enabled", PathText:"Multi-Push > WebHook > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"Url", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_ADMIN_NOTIFY_WEBHOOK_URL", Path:"admin_notify.webhook.url", PathText:"Multi-Push > WebHook > Url", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Url"},
|
||||
meta.OptionsMeta{Title:"App Key", Desc:"for generation of JWT", Type:"string", Options:[]string(nil), Env:"ATK_APP_KEY", Path:"app_key", PathText:"App Key", Default:"", IsRoot:true, HasChild:false, AllowsSet:true, CommentRaw:"App Key (for generation of JWT)"},
|
||||
meta.OptionsMeta{Title:"Social Login", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_AUTH", Path:"auth", PathText:"Social Login", Default:interface {}(nil), IsRoot:true, HasChild:true, AllowsSet:false, CommentRaw:"Social Login"},
|
||||
meta.OptionsMeta{Title:"Allow anonymous commenting", Desc:"Allow skipping verification, only fill in an anonymous nickname and email", Type:"bool", Options:[]string(nil), Env:"ATK_AUTH_ANONYMOUS", Path:"auth.anonymous", PathText:"Social Login > Allow anonymous commenting", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Allow anonymous commenting (Allow skipping verification, only fill in an anonymous nickname and email)"},
|
||||
meta.OptionsMeta{Title:"Apple", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_AUTH_APPLE", Path:"auth.apple", PathText:"Social Login > Apple", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Apple"},
|
||||
meta.OptionsMeta{Title:"ClientId", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_APPLE_CLIENT_ID", Path:"auth.apple.client_id", PathText:"Social Login > Apple > ClientId", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientId"},
|
||||
meta.OptionsMeta{Title:"ClientSecret", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_APPLE_CLIENT_SECRET", Path:"auth.apple.client_secret", PathText:"Social Login > Apple > ClientSecret", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientSecret"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_AUTH_APPLE_ENABLED", Path:"auth.apple.enabled", PathText:"Social Login > Apple > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"Auth0", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_AUTH_AUTH0", Path:"auth.auth0", PathText:"Social Login > Auth0", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Auth0"},
|
||||
meta.OptionsMeta{Title:"ClientId", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_AUTH0_CLIENT_ID", Path:"auth.auth0.client_id", PathText:"Social Login > Auth0 > ClientId", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientId"},
|
||||
meta.OptionsMeta{Title:"ClientSecret", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_AUTH0_CLIENT_SECRET", Path:"auth.auth0.client_secret", PathText:"Social Login > Auth0 > ClientSecret", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientSecret"},
|
||||
meta.OptionsMeta{Title:"Domain", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_AUTH0_DOMAIN", Path:"auth.auth0.domain", PathText:"Social Login > Auth0 > Domain", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Domain"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_AUTH_AUTH0_ENABLED", Path:"auth.auth0.enabled", PathText:"Social Login > Auth0 > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"Callback URL", Desc:"https://example.com/api/v2/auth/{provider}/callback", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_CALLBACK", Path:"auth.callback", PathText:"Social Login > Callback URL", Default:"http://localhost:23366/api/v2/auth/{provider}/callback", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Callback URL (https://example.com/api/v2/auth/{provider}/callback)"},
|
||||
meta.OptionsMeta{Title:"Discord", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_AUTH_DISCORD", Path:"auth.discord", PathText:"Social Login > Discord", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Discord"},
|
||||
meta.OptionsMeta{Title:"ClientId", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_DISCORD_CLIENT_ID", Path:"auth.discord.client_id", PathText:"Social Login > Discord > ClientId", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientId"},
|
||||
meta.OptionsMeta{Title:"ClientSecret", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_DISCORD_CLIENT_SECRET", Path:"auth.discord.client_secret", PathText:"Social Login > Discord > ClientSecret", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientSecret"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_AUTH_DISCORD_ENABLED", Path:"auth.discord.enabled", PathText:"Social Login > Discord > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"Email", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_AUTH_EMAIL", Path:"auth.email", PathText:"Social Login > Email", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Email"},
|
||||
meta.OptionsMeta{Title:"Enable email password login", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_AUTH_EMAIL_ENABLED", Path:"auth.email.enabled", PathText:"Social Login > Email > Enable email password login", Default:true, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enable email password login"},
|
||||
meta.OptionsMeta{Title:"Verification email subject", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_EMAIL_VERIFY_SUBJECT", Path:"auth.email.verify_subject", PathText:"Social Login > Email > Verification email subject", Default:"Your Code - {{code}}", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Verification email subject"},
|
||||
meta.OptionsMeta{Title:"Verification email template", Desc:"set to file path to use custom template", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_EMAIL_VERIFY_TPL", Path:"auth.email.verify_tpl", PathText:"Social Login > Email > Verification email template", Default:"default", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Verification email template (set to file path to use custom template)"},
|
||||
meta.OptionsMeta{Title:"Enable Social Login", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_AUTH_ENABLED", Path:"auth.enabled", PathText:"Social Login > Enable Social Login", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enable Social Login"},
|
||||
meta.OptionsMeta{Title:"Facebook", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_AUTH_FACEBOOK", Path:"auth.facebook", PathText:"Social Login > Facebook", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Facebook"},
|
||||
meta.OptionsMeta{Title:"ClientId", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_FACEBOOK_CLIENT_ID", Path:"auth.facebook.client_id", PathText:"Social Login > Facebook > ClientId", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientId"},
|
||||
meta.OptionsMeta{Title:"ClientSecret", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_FACEBOOK_CLIENT_SECRET", Path:"auth.facebook.client_secret", PathText:"Social Login > Facebook > ClientSecret", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientSecret"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_AUTH_FACEBOOK_ENABLED", Path:"auth.facebook.enabled", PathText:"Social Login > Facebook > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"Gitea", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_AUTH_GITEA", Path:"auth.gitea", PathText:"Social Login > Gitea", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Gitea"},
|
||||
meta.OptionsMeta{Title:"ClientId", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_GITEA_CLIENT_ID", Path:"auth.gitea.client_id", PathText:"Social Login > Gitea > ClientId", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientId"},
|
||||
meta.OptionsMeta{Title:"ClientSecret", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_GITEA_CLIENT_SECRET", Path:"auth.gitea.client_secret", PathText:"Social Login > Gitea > ClientSecret", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientSecret"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_AUTH_GITEA_ENABLED", Path:"auth.gitea.enabled", PathText:"Social Login > Gitea > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"GitHub", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_AUTH_GITHUB", Path:"auth.github", PathText:"Social Login > GitHub", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"GitHub"},
|
||||
meta.OptionsMeta{Title:"ClientId", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_GITHUB_CLIENT_ID", Path:"auth.github.client_id", PathText:"Social Login > GitHub > ClientId", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientId"},
|
||||
meta.OptionsMeta{Title:"ClientSecret", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_GITHUB_CLIENT_SECRET", Path:"auth.github.client_secret", PathText:"Social Login > GitHub > ClientSecret", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientSecret"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_AUTH_GITHUB_ENABLED", Path:"auth.github.enabled", PathText:"Social Login > GitHub > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"GitLab", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_AUTH_GITLAB", Path:"auth.gitlab", PathText:"Social Login > GitLab", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"GitLab"},
|
||||
meta.OptionsMeta{Title:"ClientId", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_GITLAB_CLIENT_ID", Path:"auth.gitlab.client_id", PathText:"Social Login > GitLab > ClientId", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientId"},
|
||||
meta.OptionsMeta{Title:"ClientSecret", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_GITLAB_CLIENT_SECRET", Path:"auth.gitlab.client_secret", PathText:"Social Login > GitLab > ClientSecret", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientSecret"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_AUTH_GITLAB_ENABLED", Path:"auth.gitlab.enabled", PathText:"Social Login > GitLab > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"Google", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_AUTH_GOOGLE", Path:"auth.google", PathText:"Social Login > Google", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Google"},
|
||||
meta.OptionsMeta{Title:"ClientId", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_GOOGLE_CLIENT_ID", Path:"auth.google.client_id", PathText:"Social Login > Google > ClientId", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientId"},
|
||||
meta.OptionsMeta{Title:"ClientSecret", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_GOOGLE_CLIENT_SECRET", Path:"auth.google.client_secret", PathText:"Social Login > Google > ClientSecret", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientSecret"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_AUTH_GOOGLE_ENABLED", Path:"auth.google.enabled", PathText:"Social Login > Google > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"Line", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_AUTH_LINE", Path:"auth.line", PathText:"Social Login > Line", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Line"},
|
||||
meta.OptionsMeta{Title:"ClientId", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_LINE_CLIENT_ID", Path:"auth.line.client_id", PathText:"Social Login > Line > ClientId", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientId"},
|
||||
meta.OptionsMeta{Title:"ClientSecret", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_LINE_CLIENT_SECRET", Path:"auth.line.client_secret", PathText:"Social Login > Line > ClientSecret", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientSecret"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_AUTH_LINE_ENABLED", Path:"auth.line.enabled", PathText:"Social Login > Line > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"Mastodon", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_AUTH_MASTODON", Path:"auth.mastodon", PathText:"Social Login > Mastodon", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Mastodon"},
|
||||
meta.OptionsMeta{Title:"ClientId", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_MASTODON_CLIENT_ID", Path:"auth.mastodon.client_id", PathText:"Social Login > Mastodon > ClientId", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientId"},
|
||||
meta.OptionsMeta{Title:"ClientSecret", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_MASTODON_CLIENT_SECRET", Path:"auth.mastodon.client_secret", PathText:"Social Login > Mastodon > ClientSecret", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientSecret"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_AUTH_MASTODON_ENABLED", Path:"auth.mastodon.enabled", PathText:"Social Login > Mastodon > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"Microsoft", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_AUTH_MICROSOFT", Path:"auth.microsoft", PathText:"Social Login > Microsoft", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Microsoft"},
|
||||
meta.OptionsMeta{Title:"ClientId", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_MICROSOFT_CLIENT_ID", Path:"auth.microsoft.client_id", PathText:"Social Login > Microsoft > ClientId", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientId"},
|
||||
meta.OptionsMeta{Title:"ClientSecret", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_MICROSOFT_CLIENT_SECRET", Path:"auth.microsoft.client_secret", PathText:"Social Login > Microsoft > ClientSecret", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientSecret"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_AUTH_MICROSOFT_ENABLED", Path:"auth.microsoft.enabled", PathText:"Social Login > Microsoft > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"Patreon", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_AUTH_PATREON", Path:"auth.patreon", PathText:"Social Login > Patreon", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Patreon"},
|
||||
meta.OptionsMeta{Title:"ClientId", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_PATREON_CLIENT_ID", Path:"auth.patreon.client_id", PathText:"Social Login > Patreon > ClientId", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientId"},
|
||||
meta.OptionsMeta{Title:"ClientSecret", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_PATREON_CLIENT_SECRET", Path:"auth.patreon.client_secret", PathText:"Social Login > Patreon > ClientSecret", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientSecret"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_AUTH_PATREON_ENABLED", Path:"auth.patreon.enabled", PathText:"Social Login > Patreon > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"Slack", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_AUTH_SLACK", Path:"auth.slack", PathText:"Social Login > Slack", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Slack"},
|
||||
meta.OptionsMeta{Title:"ClientId", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_SLACK_CLIENT_ID", Path:"auth.slack.client_id", PathText:"Social Login > Slack > ClientId", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientId"},
|
||||
meta.OptionsMeta{Title:"ClientSecret", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_SLACK_CLIENT_SECRET", Path:"auth.slack.client_secret", PathText:"Social Login > Slack > ClientSecret", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientSecret"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_AUTH_SLACK_ENABLED", Path:"auth.slack.enabled", PathText:"Social Login > Slack > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"SSO", Desc:"Exchange external OIDC access tokens for Artalk login sessions, verified via the IdP's /userinfo endpoint", Type:"<nil>", Options:[]string(nil), Env:"ATK_AUTH_SSO", Path:"auth.sso", PathText:"Social Login > SSO", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"SSO (Exchange external OIDC access tokens for Artalk login sessions, verified via the IdP's /userinfo endpoint) -- Use this when the embedding app already runs OIDC and you want Artalk to inherit that session without its own popup login UI. --"},
|
||||
meta.OptionsMeta{Title:"Enable SSO token exchange", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_AUTH_SSO_ENABLED", Path:"auth.sso.enabled", PathText:"Social Login > SSO > Enable SSO token exchange", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enable SSO token exchange"},
|
||||
meta.OptionsMeta{Title:"OIDC issuer URL", Desc:"e.g. \"tenant.auth0.com\" or \"https://tenant.auth0.com\"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_SSO_ISSUER", Path:"auth.sso.issuer", PathText:"Social Login > SSO > OIDC issuer URL", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"OIDC issuer URL (e.g. \"tenant.auth0.com\" or \"https://tenant.auth0.com\")"},
|
||||
meta.OptionsMeta{Title:"Steam", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_AUTH_STEAM", Path:"auth.steam", PathText:"Social Login > Steam", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Steam"},
|
||||
meta.OptionsMeta{Title:"ApiKey", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_STEAM_API_KEY", Path:"auth.steam.api_key", PathText:"Social Login > Steam > ApiKey", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ApiKey"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_AUTH_STEAM_ENABLED", Path:"auth.steam.enabled", PathText:"Social Login > Steam > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"Tiktok", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_AUTH_TIKTOK", Path:"auth.tiktok", PathText:"Social Login > Tiktok", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Tiktok"},
|
||||
meta.OptionsMeta{Title:"ClientId", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_TIKTOK_CLIENT_ID", Path:"auth.tiktok.client_id", PathText:"Social Login > Tiktok > ClientId", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientId"},
|
||||
meta.OptionsMeta{Title:"ClientSecret", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_TIKTOK_CLIENT_SECRET", Path:"auth.tiktok.client_secret", PathText:"Social Login > Tiktok > ClientSecret", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientSecret"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_AUTH_TIKTOK_ENABLED", Path:"auth.tiktok.enabled", PathText:"Social Login > Tiktok > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"Twitter", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_AUTH_TWITTER", Path:"auth.twitter", PathText:"Social Login > Twitter", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Twitter"},
|
||||
meta.OptionsMeta{Title:"ClientId", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_TWITTER_CLIENT_ID", Path:"auth.twitter.client_id", PathText:"Social Login > Twitter > ClientId", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientId"},
|
||||
meta.OptionsMeta{Title:"ClientSecret", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_TWITTER_CLIENT_SECRET", Path:"auth.twitter.client_secret", PathText:"Social Login > Twitter > ClientSecret", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientSecret"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_AUTH_TWITTER_ENABLED", Path:"auth.twitter.enabled", PathText:"Social Login > Twitter > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"WeChat", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_AUTH_WECHAT", Path:"auth.wechat", PathText:"Social Login > WeChat", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"WeChat"},
|
||||
meta.OptionsMeta{Title:"ClientId", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_WECHAT_CLIENT_ID", Path:"auth.wechat.client_id", PathText:"Social Login > WeChat > ClientId", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientId"},
|
||||
meta.OptionsMeta{Title:"ClientSecret", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_AUTH_WECHAT_CLIENT_SECRET", Path:"auth.wechat.client_secret", PathText:"Social Login > WeChat > ClientSecret", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ClientSecret"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_AUTH_WECHAT_ENABLED", Path:"auth.wechat.enabled", PathText:"Social Login > WeChat > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"Cache", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_CACHE", Path:"cache", PathText:"Cache", Default:interface {}(nil), IsRoot:true, HasChild:true, AllowsSet:false, CommentRaw:"Cache"},
|
||||
meta.OptionsMeta{Title:"Enable cache", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_CACHE_ENABLED", Path:"cache.enabled", PathText:"Cache > Enable cache", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enable cache"},
|
||||
meta.OptionsMeta{Title:"Cache expiration time", Desc:"in minutes", Type:"uint64", Options:[]string(nil), Env:"ATK_CACHE_EXPIRES", Path:"cache.expires", PathText:"Cache > Cache expiration time", Default:0x1e, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Cache expiration time (in minutes)"},
|
||||
meta.OptionsMeta{Title:"Redis config", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_CACHE_REDIS", Path:"cache.redis", PathText:"Cache > Redis config", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Redis config"},
|
||||
meta.OptionsMeta{Title:"Redis database number", Desc:"e.g. 0", Type:"uint64", Options:[]string(nil), Env:"ATK_CACHE_REDIS_DB", Path:"cache.redis.db", PathText:"Cache > Redis config > Redis database number", Default:0x0, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Redis database number (e.g. 0)"},
|
||||
meta.OptionsMeta{Title:"Connection type", Desc:"", Type:"string", Options:[]string{"tcp", "unix"}, Env:"ATK_CACHE_REDIS_NETWORK", Path:"cache.redis.network", PathText:"Cache > Redis config > Connection type", Default:"tcp", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Connection type [\"tcp\", \"unix\"]"},
|
||||
meta.OptionsMeta{Title:"Redis password", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_CACHE_REDIS_PASSWORD", Path:"cache.redis.password", PathText:"Cache > Redis config > Redis password", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Redis password"},
|
||||
meta.OptionsMeta{Title:"Redis username", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_CACHE_REDIS_USERNAME", Path:"cache.redis.username", PathText:"Cache > Redis config > Redis username", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Redis username"},
|
||||
meta.OptionsMeta{Title:"Cache server address", Desc:"e.g. \"localhost:6379\"", Type:"string", Options:[]string(nil), Env:"ATK_CACHE_SERVER", Path:"cache.server", PathText:"Cache > Cache server address", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"-- The following is not necessary for `builtin` cache -- Cache server address (e.g. \"localhost:6379\")"},
|
||||
meta.OptionsMeta{Title:"Cache type", Desc:"", Type:"string", Options:[]string{"redis", "memcache", "builtin"}, Env:"ATK_CACHE_TYPE", Path:"cache.type", PathText:"Cache > Cache type", Default:"builtin", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Cache type [\"redis\", \"memcache\", \"builtin\"]"},
|
||||
meta.OptionsMeta{Title:"Cache warm up", Desc:"warm up cache when program starts", Type:"bool", Options:[]string(nil), Env:"ATK_CACHE_WARM_UP", Path:"cache.warm_up", PathText:"Cache > Cache warm up", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Cache warm up (warm up cache when program starts)"},
|
||||
meta.OptionsMeta{Title:"Captcha", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_CAPTCHA", Path:"captcha", PathText:"Captcha", Default:interface {}(nil), IsRoot:true, HasChild:true, AllowsSet:false, CommentRaw:"Captcha"},
|
||||
meta.OptionsMeta{Title:"Action limit", Desc:"the number of actions required to activate captcha", Type:"uint64", Options:[]string(nil), Env:"ATK_CAPTCHA_ACTION_LIMIT", Path:"captcha.action_limit", PathText:"Captcha > Action limit", Default:0x3, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Action limit (the number of actions required to activate captcha)"},
|
||||
meta.OptionsMeta{Title:"Reset Timeout", Desc:"timeout to reset action counter. unit: s, set to -1 to disable", Type:"uint64", Options:[]string(nil), Env:"ATK_CAPTCHA_ACTION_RESET", Path:"captcha.action_reset", PathText:"Captcha > Reset Timeout", Default:0x3c, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Reset Timeout (timeout to reset action counter. unit: s, set to -1 to disable)"},
|
||||
meta.OptionsMeta{Title:"Captcha is required always", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_CAPTCHA_ALWAYS", Path:"captcha.always", PathText:"Captcha > Captcha is required always", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Captcha is required always"},
|
||||
meta.OptionsMeta{Title:"Captcha type", Desc:"", Type:"string", Options:[]string{"image", "turnstile", "recaptcha", "hcaptcha", "geetest"}, Env:"ATK_CAPTCHA_CAPTCHA_TYPE", Path:"captcha.captcha_type", PathText:"Captcha > Captcha type", Default:"image", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Captcha type [\"image\", \"turnstile\", \"recaptcha\", \"hcaptcha\", \"geetest\"]"},
|
||||
meta.OptionsMeta{Title:"Enable captcha", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_CAPTCHA_ENABLED", Path:"captcha.enabled", PathText:"Captcha > Enable captcha", Default:true, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enable captcha"},
|
||||
meta.OptionsMeta{Title:"Geetest", Desc:"https://www.geetest.com", Type:"<nil>", Options:[]string(nil), Env:"ATK_CAPTCHA_GEETEST", Path:"captcha.geetest", PathText:"Captcha > Geetest", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Geetest (https://www.geetest.com)"},
|
||||
meta.OptionsMeta{Title:"CaptchaId", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_CAPTCHA_GEETEST_CAPTCHA_ID", Path:"captcha.geetest.captcha_id", PathText:"Captcha > Geetest > CaptchaId", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"CaptchaId"},
|
||||
meta.OptionsMeta{Title:"CaptchaKey", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_CAPTCHA_GEETEST_CAPTCHA_KEY", Path:"captcha.geetest.captcha_key", PathText:"Captcha > Geetest > CaptchaKey", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"CaptchaKey"},
|
||||
meta.OptionsMeta{Title:"hCaptcha", Desc:"https://www.hcaptcha.com/", Type:"<nil>", Options:[]string(nil), Env:"ATK_CAPTCHA_HCAPTCHA", Path:"captcha.hcaptcha", PathText:"Captcha > hCaptcha", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"hCaptcha (https://www.hcaptcha.com/)"},
|
||||
meta.OptionsMeta{Title:"SecretKey", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_CAPTCHA_HCAPTCHA_SECRET_KEY", Path:"captcha.hcaptcha.secret_key", PathText:"Captcha > hCaptcha > SecretKey", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"SecretKey"},
|
||||
meta.OptionsMeta{Title:"SiteKey", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_CAPTCHA_HCAPTCHA_SITE_KEY", Path:"captcha.hcaptcha.site_key", PathText:"Captcha > hCaptcha > SiteKey", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"SiteKey"},
|
||||
meta.OptionsMeta{Title:"reCaptcha", Desc:"https://www.google.com/recaptcha/about/", Type:"<nil>", Options:[]string(nil), Env:"ATK_CAPTCHA_RECAPTCHA", Path:"captcha.recaptcha", PathText:"Captcha > reCaptcha", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"reCaptcha (https://www.google.com/recaptcha/about/)"},
|
||||
meta.OptionsMeta{Title:"SecretKey", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_CAPTCHA_RECAPTCHA_SECRET_KEY", Path:"captcha.recaptcha.secret_key", PathText:"Captcha > reCaptcha > SecretKey", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"SecretKey"},
|
||||
meta.OptionsMeta{Title:"SiteKey", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_CAPTCHA_RECAPTCHA_SITE_KEY", Path:"captcha.recaptcha.site_key", PathText:"Captcha > reCaptcha > SiteKey", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"SiteKey"},
|
||||
meta.OptionsMeta{Title:"Turnstile", Desc:"https://www.cloudflare.com/products/turnstile/", Type:"<nil>", Options:[]string(nil), Env:"ATK_CAPTCHA_TURNSTILE", Path:"captcha.turnstile", PathText:"Captcha > Turnstile", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Turnstile (https://www.cloudflare.com/products/turnstile/)"},
|
||||
meta.OptionsMeta{Title:"SecretKey", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_CAPTCHA_TURNSTILE_SECRET_KEY", Path:"captcha.turnstile.secret_key", PathText:"Captcha > Turnstile > SecretKey", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"SecretKey"},
|
||||
meta.OptionsMeta{Title:"SiteKey", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_CAPTCHA_TURNSTILE_SITE_KEY", Path:"captcha.turnstile.site_key", PathText:"Captcha > Turnstile > SiteKey", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"SiteKey"},
|
||||
meta.OptionsMeta{Title:"Database", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_DB", Path:"db", PathText:"Database", Default:interface {}(nil), IsRoot:true, HasChild:true, AllowsSet:false, CommentRaw:"Database"},
|
||||
meta.OptionsMeta{Title:"Database charset", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_DB_CHARSET", Path:"db.charset", PathText:"Database > Database charset", Default:"utf8mb4", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Database charset"},
|
||||
meta.OptionsMeta{Title:"Database file", Desc:"only for SQLite", Type:"string", Options:[]string(nil), Env:"ATK_DB_FILE", Path:"db.file", PathText:"Database > Database file", Default:"./data/artalk.db", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Database file (only for SQLite)"},
|
||||
meta.OptionsMeta{Title:"Host address", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_DB_HOST", Path:"db.host", PathText:"Database > Host address", Default:"localhost", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Host address"},
|
||||
meta.OptionsMeta{Title:"Database name", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_DB_NAME", Path:"db.name", PathText:"Database > Database name", Default:"artalk", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"-- The following is not necessary for SQLite -- Database name"},
|
||||
meta.OptionsMeta{Title:"Database password", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_DB_PASSWORD", Path:"db.password", PathText:"Database > Database password", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Database password"},
|
||||
meta.OptionsMeta{Title:"Host port", Desc:"", Type:"uint64", Options:[]string(nil), Env:"ATK_DB_PORT", Path:"db.port", PathText:"Database > Host port", Default:0xcea, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Host port"},
|
||||
meta.OptionsMeta{Title:"Prepared Statement", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_DB_PREPARE_STMT", Path:"db.prepare_stmt", PathText:"Database > Prepared Statement", Default:true, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Prepared Statement"},
|
||||
meta.OptionsMeta{Title:"Enable SSL mode", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_DB_SSL", Path:"db.ssl", PathText:"Database > Enable SSL mode", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enable SSL mode"},
|
||||
meta.OptionsMeta{Title:"Table prefix", Desc:"e.g. \"atk_\"", Type:"string", Options:[]string(nil), Env:"ATK_DB_TABLE_PREFIX", Path:"db.table_prefix", PathText:"Database > Table prefix", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Table prefix (e.g. \"atk_\")"},
|
||||
meta.OptionsMeta{Title:"Database type", Desc:"", Type:"string", Options:[]string{"sqlite", "mysql", "pgsql", "mssql"}, Env:"ATK_DB_TYPE", Path:"db.type", PathText:"Database > Database type", Default:"sqlite", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Database type [\"sqlite\", \"mysql\", \"pgsql\", \"mssql\"]"},
|
||||
meta.OptionsMeta{Title:"Database user", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_DB_USER", Path:"db.user", PathText:"Database > Database user", Default:"root", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Database user"},
|
||||
meta.OptionsMeta{Title:"Debug mode", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_DEBUG", Path:"debug", PathText:"Debug mode", Default:false, IsRoot:true, HasChild:false, AllowsSet:true, CommentRaw:"Debug mode"},
|
||||
meta.OptionsMeta{Title:"Email", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_EMAIL", Path:"email", PathText:"Email", Default:interface {}(nil), IsRoot:true, HasChild:true, AllowsSet:false, CommentRaw:"Email"},
|
||||
meta.OptionsMeta{Title:"Aliyun mail push", Desc:"set send method to \"ali_dm\" to enable; see: https://help.aliyun.com/document_detail/29444.html", Type:"<nil>", Options:[]string(nil), Env:"ATK_EMAIL_ALI_DM", Path:"email.ali_dm", PathText:"Email > Aliyun mail push", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Aliyun mail push (set send method to \"ali_dm\" to enable; see: https://help.aliyun.com/document_detail/29444.html)"},
|
||||
meta.OptionsMeta{Title:"AccessKeyId", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_EMAIL_ALI_DM_ACCESS_KEY_ID", Path:"email.ali_dm.access_key_id", PathText:"Email > Aliyun mail push > AccessKeyId", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"AccessKeyId"},
|
||||
meta.OptionsMeta{Title:"AccessKeySecret", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_EMAIL_ALI_DM_ACCESS_KEY_SECRET", Path:"email.ali_dm.access_key_secret", PathText:"Email > Aliyun mail push > AccessKeySecret", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"AccessKeySecret"},
|
||||
meta.OptionsMeta{Title:"AccountName", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_EMAIL_ALI_DM_ACCOUNT_NAME", Path:"email.ali_dm.account_name", PathText:"Email > Aliyun mail push > AccountName", Default:"noreply@example.com", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"AccountName"},
|
||||
meta.OptionsMeta{Title:"Enable email notification", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_EMAIL_ENABLED", Path:"email.enabled", PathText:"Email > Enable email notification", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enable email notification"},
|
||||
meta.OptionsMeta{Title:"Email subject", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_EMAIL_MAIL_SUBJECT", Path:"email.mail_subject", PathText:"Email > Email subject", Default:"[{{site_name}}] You got a reply from @{{reply_nick}}", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Email subject"},
|
||||
meta.OptionsMeta{Title:"Email template file", Desc:"set to file path to use custom template", Type:"string", Options:[]string(nil), Env:"ATK_EMAIL_MAIL_TPL", Path:"email.mail_tpl", PathText:"Email > Email template file", Default:"default", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Email template file (set to file path to use custom template)"},
|
||||
meta.OptionsMeta{Title:"Email address of sender", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_EMAIL_SEND_ADDR", Path:"email.send_addr", PathText:"Email > Email address of sender", Default:"noreply@example.com", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Email address of sender"},
|
||||
meta.OptionsMeta{Title:"Nick name of sender", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_EMAIL_SEND_NAME", Path:"email.send_name", PathText:"Email > Nick name of sender", Default:"{{reply_nick}}", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Nick name of sender"},
|
||||
meta.OptionsMeta{Title:"Send method", Desc:"", Type:"string", Options:[]string{"smtp", "ali_dm", "sendmail"}, Env:"ATK_EMAIL_SEND_TYPE", Path:"email.send_type", PathText:"Email > Send method", Default:"smtp", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Send method [\"smtp\", \"ali_dm\", \"sendmail\"]"},
|
||||
meta.OptionsMeta{Title:"SMTP send", Desc:"set send method to \"smtp\" to enable", Type:"<nil>", Options:[]string(nil), Env:"ATK_EMAIL_SMTP", Path:"email.smtp", PathText:"Email > SMTP send", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"SMTP send (set send method to \"smtp\" to enable)"},
|
||||
meta.OptionsMeta{Title:"Email address of sender", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_EMAIL_SMTP_HOST", Path:"email.smtp.host", PathText:"Email > SMTP send > Email address of sender", Default:"smtp.qq.com", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Email address of sender"},
|
||||
meta.OptionsMeta{Title:"Password", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_EMAIL_SMTP_PASSWORD", Path:"email.smtp.password", PathText:"Email > SMTP send > Password", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Password"},
|
||||
meta.OptionsMeta{Title:"Email port", Desc:"", Type:"uint64", Options:[]string(nil), Env:"ATK_EMAIL_SMTP_PORT", Path:"email.smtp.port", PathText:"Email > SMTP send > Email port", Default:0x24b, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Email port"},
|
||||
meta.OptionsMeta{Title:"Email address of sender", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_EMAIL_SMTP_USERNAME", Path:"email.smtp.username", PathText:"Email > SMTP send > Email address of sender", Default:"example@qq.com", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Email address of sender"},
|
||||
meta.OptionsMeta{Title:"UI Settings", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_FRONTEND", Path:"frontend", PathText:"UI Settings", Default:interface {}(nil), IsRoot:true, HasChild:true, AllowsSet:false, CommentRaw:"UI Settings"},
|
||||
meta.OptionsMeta{Title:"Dark mode", Desc:"", Type:"string", Options:[]string{"inherit", "auto"}, Env:"ATK_FRONTEND_DARKMODE", Path:"frontend.darkMode", PathText:"UI Settings > Dark mode", Default:"inherit", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Dark mode [\"inherit\", \"auto\"]"},
|
||||
meta.OptionsMeta{Title:"Movable comment box", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_FRONTEND_EDITORTRAVEL", Path:"frontend.editorTravel", PathText:"UI Settings > Movable comment box", Default:true, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Movable comment box"},
|
||||
meta.OptionsMeta{Title:"Emoticons", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_FRONTEND_EMOTICONS", Path:"frontend.emoticons", PathText:"UI Settings > Emoticons", Default:"https://cdn.jsdelivr.net/gh/ArtalkJS/Emoticons/grps/default.json", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Emoticons"},
|
||||
meta.OptionsMeta{Title:"Flatten mode", Desc:"", Type:"string", Options:[]string{"auto", "true", "false"}, Env:"ATK_FRONTEND_FLATMODE", Path:"frontend.flatMode", PathText:"UI Settings > Flatten mode", Default:"auto", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Flatten mode [\"auto\", true, false]"},
|
||||
meta.OptionsMeta{Title:"Gravatar", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_FRONTEND_GRAVATAR", Path:"frontend.gravatar", PathText:"UI Settings > Gravatar", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Gravatar"},
|
||||
meta.OptionsMeta{Title:"API URL", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_FRONTEND_GRAVATAR_MIRROR", Path:"frontend.gravatar.mirror", PathText:"UI Settings > Gravatar > API URL", Default:"https://www.gravatar.com/avatar/", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"API URL"},
|
||||
meta.OptionsMeta{Title:"API parameters", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_FRONTEND_GRAVATAR_PARAMS", Path:"frontend.gravatar.params", PathText:"UI Settings > Gravatar > API parameters", Default:"sha256=1&d=mp&s=240", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"API parameters"},
|
||||
meta.OptionsMeta{Title:"Content height limit", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_FRONTEND_HEIGHTLIMIT", Path:"frontend.heightLimit", PathText:"UI Settings > Content height limit", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Content height limit"},
|
||||
meta.OptionsMeta{Title:"Sub-comment area height limit", Desc:"unit: px", Type:"uint64", Options:[]string(nil), Env:"ATK_FRONTEND_HEIGHTLIMIT_CHILDREN", Path:"frontend.heightLimit.children", PathText:"UI Settings > Content height limit > Sub-comment area height limit", Default:0x190, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Sub-comment area height limit (unit: px)"},
|
||||
meta.OptionsMeta{Title:"Comment content height limit", Desc:"unit: px", Type:"uint64", Options:[]string(nil), Env:"ATK_FRONTEND_HEIGHTLIMIT_CONTENT", Path:"frontend.heightLimit.content", PathText:"UI Settings > Content height limit > Comment content height limit", Default:0x12c, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Comment content height limit (unit: px)"},
|
||||
meta.OptionsMeta{Title:"Scrollable", Desc:"scrollable height limit area", Type:"bool", Options:[]string(nil), Env:"ATK_FRONTEND_HEIGHTLIMIT_SCROLLABLE", Path:"frontend.heightLimit.scrollable", PathText:"UI Settings > Content height limit > Scrollable", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Scrollable (scrollable height limit area)"},
|
||||
meta.OptionsMeta{Title:"Image lazy load", Desc:"", Type:"bool", Options:[]string{"false", "native", "data-src"}, Env:"ATK_FRONTEND_IMGLAZYLOAD", Path:"frontend.imgLazyLoad", PathText:"UI Settings > Image lazy load", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Image lazy load [false, \"native\", \"data-src\"]"},
|
||||
meta.OptionsMeta{Title:"Comment sorting", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_FRONTEND_LISTSORT", Path:"frontend.listSort", PathText:"UI Settings > Comment sorting", Default:true, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Comment sorting"},
|
||||
meta.OptionsMeta{Title:"Maximum nesting level", Desc:"", Type:"uint64", Options:[]string(nil), Env:"ATK_FRONTEND_NESTMAX", Path:"frontend.nestMax", PathText:"UI Settings > Maximum nesting level", Default:0x2, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Maximum nesting level"},
|
||||
meta.OptionsMeta{Title:"Nesting comment sorting rules", Desc:"", Type:"string", Options:[]string{"DATE_ASC", "DATE_DESC", "VOTE_UP_DESC"}, Env:"ATK_FRONTEND_NESTSORT", Path:"frontend.nestSort", PathText:"UI Settings > Nesting comment sorting rules", Default:"DATE_ASC", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Nesting comment sorting rules [\"DATE_ASC\", \"DATE_DESC\", \"VOTE_UP_DESC\"]"},
|
||||
meta.OptionsMeta{Title:"Text to display when there is", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_FRONTEND_NOCOMMENT", Path:"frontend.noComment", PathText:"UI Settings > Text to display when there is", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Text to display when there is"},
|
||||
meta.OptionsMeta{Title:"Comment pagination", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_FRONTEND_PAGINATION", Path:"frontend.pagination", PathText:"UI Settings > Comment pagination", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Comment pagination"},
|
||||
meta.OptionsMeta{Title:"Scroll loading", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_FRONTEND_PAGINATION_AUTOLOAD", Path:"frontend.pagination.autoLoad", PathText:"UI Settings > Comment pagination > Scroll loading", Default:true, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Scroll loading"},
|
||||
meta.OptionsMeta{Title:"Number of comments per page", Desc:"", Type:"uint64", Options:[]string(nil), Env:"ATK_FRONTEND_PAGINATION_PAGESIZE", Path:"frontend.pagination.pageSize", PathText:"UI Settings > Comment pagination > Number of comments per page", Default:0x14, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Number of comments per page"},
|
||||
meta.OptionsMeta{Title:"Load more mode", Desc:"disabled to use pagination bar", Type:"bool", Options:[]string(nil), Env:"ATK_FRONTEND_PAGINATION_READMORE", Path:"frontend.pagination.readMore", PathText:"UI Settings > Comment pagination > Load more mode", Default:true, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Load more mode (disabled to use pagination bar)"},
|
||||
meta.OptionsMeta{Title:"Comment box placeholder", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_FRONTEND_PLACEHOLDER", Path:"frontend.placeholder", PathText:"UI Settings > Comment box placeholder", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Comment box placeholder"},
|
||||
meta.OptionsMeta{Title:"Plugins", Desc:"", Type:"[]interface {}", Options:[]string(nil), Env:"ATK_FRONTEND_PLUGINURLS", Path:"frontend.pluginURLs", PathText:"UI Settings > Plugins", Default:[]interface {}{}, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Plugins"},
|
||||
meta.OptionsMeta{Title:"Editor real-time preview", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_FRONTEND_PREVIEW", Path:"frontend.preview", PathText:"UI Settings > Editor real-time preview", Default:true, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Editor real-time preview"},
|
||||
meta.OptionsMeta{Title:"Request timeout", Desc:"unit: ms", Type:"uint64", Options:[]string(nil), Env:"ATK_FRONTEND_REQTIMEOUT", Path:"frontend.reqTimeout", PathText:"UI Settings > Request timeout", Default:0x3a98, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Request timeout (unit: ms)"},
|
||||
meta.OptionsMeta{Title:"Text of the send button", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_FRONTEND_SENDBTN", Path:"frontend.sendBtn", PathText:"UI Settings > Text of the send button", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Text of the send button"},
|
||||
meta.OptionsMeta{Title:"User UA badge", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_FRONTEND_UABADGE", Path:"frontend.uaBadge", PathText:"UI Settings > User UA badge", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"User UA badge"},
|
||||
meta.OptionsMeta{Title:"Version check", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_FRONTEND_VERSIONCHECK", Path:"frontend.versionCheck", PathText:"UI Settings > Version check", Default:true, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Version check"},
|
||||
meta.OptionsMeta{Title:"Vote button", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_FRONTEND_VOTE", Path:"frontend.vote", PathText:"UI Settings > Vote button", Default:true, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Vote button"},
|
||||
meta.OptionsMeta{Title:"Vote down button", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_FRONTEND_VOTEDOWN", Path:"frontend.voteDown", PathText:"UI Settings > Vote down button", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Vote down button"},
|
||||
meta.OptionsMeta{Title:"Listen host", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_HOST", Path:"host", PathText:"Listen host", Default:"0.0.0.0", IsRoot:true, HasChild:false, AllowsSet:true, CommentRaw:"Listen host"},
|
||||
meta.OptionsMeta{Title:"Web server", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_HTTP", Path:"http", PathText:"Web server", Default:interface {}(nil), IsRoot:true, HasChild:true, AllowsSet:false, CommentRaw:"Web server"},
|
||||
meta.OptionsMeta{Title:"Body size limit", Desc:"unit: MB", Type:"uint64", Options:[]string(nil), Env:"ATK_HTTP_BODY_LIMIT", Path:"http.body_limit", PathText:"Web server > Body size limit", Default:0x64, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Body size limit (unit: MB)"},
|
||||
meta.OptionsMeta{Title:"Proxy Header", Desc:"fill `X-Forwarded-For` to get user real IP if behind a trusted reverse proxy or CDN", Type:"string", Options:[]string(nil), Env:"ATK_HTTP_PROXY_HEADER", Path:"http.proxy_header", PathText:"Web server > Proxy Header", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Proxy Header (fill `X-Forwarded-For` to get user real IP if behind a trusted reverse proxy or CDN)"},
|
||||
meta.OptionsMeta{Title:"Upload", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_IMG_UPLOAD", Path:"img_upload", PathText:"Upload", Default:interface {}(nil), IsRoot:true, HasChild:true, AllowsSet:false, CommentRaw:"Upload"},
|
||||
meta.OptionsMeta{Title:"Enable image upload", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_IMG_UPLOAD_ENABLED", Path:"img_upload.enabled", PathText:"Upload > Enable image upload", Default:true, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enable image upload"},
|
||||
meta.OptionsMeta{Title:"Image size limit", Desc:"unit: MB", Type:"uint64", Options:[]string(nil), Env:"ATK_IMG_UPLOAD_MAX_SIZE", Path:"img_upload.max_size", PathText:"Upload > Image size limit", Default:0x5, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Image size limit (unit: MB)"},
|
||||
meta.OptionsMeta{Title:"Image storage", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_IMG_UPLOAD_PATH", Path:"img_upload.path", PathText:"Upload > Image storage", Default:"./data/artalk-img/", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Image storage"},
|
||||
meta.OptionsMeta{Title:"Image link base path", Desc:"default: \"/static/images/\"", Type:"<nil>", Options:[]string(nil), Env:"ATK_IMG_UPLOAD_PUBLIC_PATH", Path:"img_upload.public_path", PathText:"Upload > Image link base path", Default:interface {}(nil), IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Image link base path (default: \"/static/images/\")"},
|
||||
meta.OptionsMeta{Title:"Upgit config", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_IMG_UPLOAD_UPGIT", Path:"img_upload.upgit", PathText:"Upload > Upgit config", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Upgit config"},
|
||||
meta.OptionsMeta{Title:"Delete local image after upload success", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_IMG_UPLOAD_UPGIT_DEL_LOCAL", Path:"img_upload.upgit.del_local", PathText:"Upload > Upgit config > Delete local image after upload success", Default:true, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Delete local image after upload success"},
|
||||
meta.OptionsMeta{Title:"Enable Upgit", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_IMG_UPLOAD_UPGIT_ENABLED", Path:"img_upload.upgit.enabled", PathText:"Upload > Upgit config > Enable Upgit", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enable Upgit"},
|
||||
meta.OptionsMeta{Title:"Command line arguments", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_IMG_UPLOAD_UPGIT_EXEC", Path:"img_upload.upgit.exec", PathText:"Upload > Upgit config > Command line arguments", Default:"upgit -c UPGIT_CONF_FILE_PATH -t /artalk-img", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Command line arguments"},
|
||||
meta.OptionsMeta{Title:"Language", Desc:"follow Unicode BCP 47", Type:"string", Options:[]string{"en", "zh-CN", "zh-TW", "ja", "fr", "ko", "ru", "tr"}, Env:"ATK_LOCALE", Path:"locale", PathText:"Language", Default:"en", IsRoot:true, HasChild:false, AllowsSet:true, CommentRaw:"Language (follow Unicode BCP 47) [\"en\", \"zh-CN\", \"zh-TW\", \"ja\", \"fr\", \"ko\", \"ru\", \"tr\"] -- see https://www.techonthenet.com/js/language_tags.php --"},
|
||||
meta.OptionsMeta{Title:"Logging", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_LOG", Path:"log", PathText:"Logging", Default:interface {}(nil), IsRoot:true, HasChild:true, AllowsSet:false, CommentRaw:"Logging"},
|
||||
meta.OptionsMeta{Title:"Login timeout", Desc:"in seconds", Type:"uint64", Options:[]string(nil), Env:"ATK_LOGIN_TIMEOUT", Path:"login_timeout", PathText:"Login timeout", Default:0x3f480, IsRoot:true, HasChild:false, AllowsSet:true, CommentRaw:"Login timeout (in seconds)"},
|
||||
meta.OptionsMeta{Title:"Enable logging", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_LOG_ENABLED", Path:"log.enabled", PathText:"Logging > Enable logging", Default:true, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enable logging"},
|
||||
meta.OptionsMeta{Title:"Log file path", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_LOG_FILENAME", Path:"log.filename", PathText:"Logging > Log file path", Default:"./data/artalk.log", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Log file path"},
|
||||
meta.OptionsMeta{Title:"Moderator", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_MODERATOR", Path:"moderator", PathText:"Moderator", Default:interface {}(nil), IsRoot:true, HasChild:true, AllowsSet:false, CommentRaw:"Moderator -- Comment examination before being public --"},
|
||||
meta.OptionsMeta{Title:"Akismet Key", Desc:"Akismet anti-spam service, https://akismet.com", Type:"string", Options:[]string(nil), Env:"ATK_MODERATOR_AKISMET_KEY", Path:"moderator.akismet_key", PathText:"Moderator > Akismet Key", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Akismet Key (Akismet anti-spam service, https://akismet.com)"},
|
||||
meta.OptionsMeta{Title:"Aliyun Content Security", Desc:"Auto review comments with Aliyun Content Security", Type:"<nil>", Options:[]string(nil), Env:"ATK_MODERATOR_ALIYUN", Path:"moderator.aliyun", PathText:"Moderator > Aliyun Content Security", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Aliyun Content Security (Auto review comments with Aliyun Content Security) -- see https://help.aliyun.com/document_detail/28417.html --"},
|
||||
meta.OptionsMeta{Title:"AccessKeyId", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_MODERATOR_ALIYUN_ACCESS_KEY_ID", Path:"moderator.aliyun.access_key_id", PathText:"Moderator > Aliyun Content Security > AccessKeyId", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"AccessKeyId"},
|
||||
meta.OptionsMeta{Title:"AccessKeySecret", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_MODERATOR_ALIYUN_ACCESS_KEY_SECRET", Path:"moderator.aliyun.access_key_secret", PathText:"Moderator > Aliyun Content Security > AccessKeySecret", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"AccessKeySecret"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_MODERATOR_ALIYUN_ENABLED", Path:"moderator.aliyun.enabled", PathText:"Moderator > Aliyun Content Security > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"Region", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_MODERATOR_ALIYUN_REGION", Path:"moderator.aliyun.region", PathText:"Moderator > Aliyun Content Security > Region", Default:"cn-shanghai", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Region"},
|
||||
meta.OptionsMeta{Title:"Block when API request fails", Desc:"set to false to let comments pass when API request fails", Type:"bool", Options:[]string(nil), Env:"ATK_MODERATOR_API_FAIL_BLOCK", Path:"moderator.api_fail_block", PathText:"Moderator > Block when API request fails", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Block when API request fails (set to false to let comments pass when API request fails)"},
|
||||
meta.OptionsMeta{Title:"Keyword filter", Desc:"local offline dictionary", Type:"<nil>", Options:[]string(nil), Env:"ATK_MODERATOR_KEYWORDS", Path:"moderator.keywords", PathText:"Moderator > Keyword filter", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Keyword filter (local offline dictionary)"},
|
||||
meta.OptionsMeta{Title:"Enable keyword filter", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_MODERATOR_KEYWORDS_ENABLED", Path:"moderator.keywords.enabled", PathText:"Moderator > Keyword filter > Enable keyword filter", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enable keyword filter"},
|
||||
meta.OptionsMeta{Title:"Dictionary file", Desc:"support multiple dictionary files", Type:"[]interface {}", Options:[]string(nil), Env:"ATK_MODERATOR_KEYWORDS_FILES", Path:"moderator.keywords.files", PathText:"Moderator > Keyword filter > Dictionary file", Default:[]interface {}{"./data/keywords_1.txt"}, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Dictionary file (support multiple dictionary files)"},
|
||||
meta.OptionsMeta{Title:"FileSep", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_MODERATOR_KEYWORDS_FILE_SEP", Path:"moderator.keywords.file_sep", PathText:"Moderator > Keyword filter > FileSep", Default:"\n", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"FileSep"},
|
||||
meta.OptionsMeta{Title:"Set to pending when match", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_MODERATOR_KEYWORDS_PENDING", Path:"moderator.keywords.pending", PathText:"Moderator > Keyword filter > Set to pending when match", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Set to pending when match"},
|
||||
meta.OptionsMeta{Title:"ReplaceTo", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_MODERATOR_KEYWORDS_REPLACE_TO", Path:"moderator.keywords.replace_to", PathText:"Moderator > Keyword filter > ReplaceTo", Default:"x", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"ReplaceTo"},
|
||||
meta.OptionsMeta{Title:"Default pending", Desc:"new comments need to be approved by admin", Type:"bool", Options:[]string(nil), Env:"ATK_MODERATOR_PENDING_DEFAULT", Path:"moderator.pending_default", PathText:"Moderator > Default pending", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Default pending (new comments need to be approved by admin)"},
|
||||
meta.OptionsMeta{Title:"Tencent Cloud Content Security", Desc:"Auto review comments with Tencent Cloud Content Security", Type:"<nil>", Options:[]string(nil), Env:"ATK_MODERATOR_TENCENT", Path:"moderator.tencent", PathText:"Moderator > Tencent Cloud Content Security", Default:interface {}(nil), IsRoot:false, HasChild:true, AllowsSet:false, CommentRaw:"Tencent Cloud Content Security (Auto review comments with Tencent Cloud Content Security) -- see https://cloud.tencent.com/document/product/1124/64508 --"},
|
||||
meta.OptionsMeta{Title:"Enabled", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_MODERATOR_TENCENT_ENABLED", Path:"moderator.tencent.enabled", PathText:"Moderator > Tencent Cloud Content Security > Enabled", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enabled"},
|
||||
meta.OptionsMeta{Title:"Region", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_MODERATOR_TENCENT_REGION", Path:"moderator.tencent.region", PathText:"Moderator > Tencent Cloud Content Security > Region", Default:"ap-guangzhou", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Region"},
|
||||
meta.OptionsMeta{Title:"SecretId", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_MODERATOR_TENCENT_SECRET_ID", Path:"moderator.tencent.secret_id", PathText:"Moderator > Tencent Cloud Content Security > SecretId", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"SecretId"},
|
||||
meta.OptionsMeta{Title:"SecretKey", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_MODERATOR_TENCENT_SECRET_KEY", Path:"moderator.tencent.secret_key", PathText:"Moderator > Tencent Cloud Content Security > SecretKey", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"SecretKey"},
|
||||
meta.OptionsMeta{Title:"Listen port", Desc:"", Type:"uint64", Options:[]string(nil), Env:"ATK_PORT", Path:"port", PathText:"Listen port", Default:0x5b46, IsRoot:true, HasChild:false, AllowsSet:true, CommentRaw:"Listen port"},
|
||||
meta.OptionsMeta{Title:"Default site name", Desc:"create when app is first launched", Type:"string", Options:[]string(nil), Env:"ATK_SITE_DEFAULT", Path:"site_default", PathText:"Default site name", Default:"Default Site", IsRoot:true, HasChild:false, AllowsSet:true, CommentRaw:"Default site name (create when app is first launched)"},
|
||||
meta.OptionsMeta{Title:"Default site url", Desc:"", Type:"string", Options:[]string(nil), Env:"ATK_SITE_URL", Path:"site_url", PathText:"Default site url", Default:"", IsRoot:true, HasChild:false, AllowsSet:true, CommentRaw:"Default site url"},
|
||||
meta.OptionsMeta{Title:"SSL", Desc:"", Type:"<nil>", Options:[]string(nil), Env:"ATK_SSL", Path:"ssl", PathText:"SSL", Default:interface {}(nil), IsRoot:true, HasChild:true, AllowsSet:false, CommentRaw:"SSL"},
|
||||
meta.OptionsMeta{Title:"Certificate file path", Desc:"e.g. \"/etc/letsencrypt/live/example.com/fullchain.pem\"", Type:"string", Options:[]string(nil), Env:"ATK_SSL_CERT_PATH", Path:"ssl.cert_path", PathText:"SSL > Certificate file path", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Certificate file path (e.g. \"/etc/letsencrypt/live/example.com/fullchain.pem\")"},
|
||||
meta.OptionsMeta{Title:"Enable SSL", Desc:"", Type:"bool", Options:[]string(nil), Env:"ATK_SSL_ENABLED", Path:"ssl.enabled", PathText:"SSL > Enable SSL", Default:false, IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Enable SSL"},
|
||||
meta.OptionsMeta{Title:"Key file path", Desc:"e.g. \"/etc/letsencrypt/live/example.com/privkey.pem\"", Type:"string", Options:[]string(nil), Env:"ATK_SSL_KEY_PATH", Path:"ssl.key_path", PathText:"SSL > Key file path", Default:"", IsRoot:false, HasChild:false, AllowsSet:true, CommentRaw:"Key file path (e.g. \"/etc/letsencrypt/live/example.com/privkey.pem\")"},
|
||||
meta.OptionsMeta{Title:"Timezone", Desc:"follow IANA Time Zone Database", Type:"string", Options:[]string(nil), Env:"ATK_TIMEZONE", Path:"timezone", PathText:"Timezone", Default:"Asia/Shanghai", IsRoot:true, HasChild:false, AllowsSet:true, CommentRaw:"Timezone (follow IANA Time Zone Database) -- see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones --"},
|
||||
meta.OptionsMeta{Title:"Trusted domains", Desc:"", Type:"[]interface {}", Options:[]string(nil), Env:"ATK_TRUSTED_DOMAINS", Path:"trusted_domains", PathText:"Trusted domains", Default:[]interface {}{}, IsRoot:true, HasChild:false, AllowsSet:true, CommentRaw:"Trusted domains -- e.g. [\"https://artalk.example.com:23366\"] add url of your site her --"},
|
||||
}
|
||||
@@ -73,6 +73,10 @@ type DBConf struct {
|
||||
Charset string `koanf:"charset" json:"charset"`
|
||||
SSL bool `koanf:"ssl" json:"ssl"`
|
||||
PrepareStmt *bool `koanf:"prepare_stmt" json:"prepare_stmt"`
|
||||
|
||||
ServerCaPath string `koanf:"server_ca_path" json:"server_ca_path"` // /path/to/server-ca.pem
|
||||
ClientCertPath string `koanf:"client_cert_path" json:"client_cert_path"` // /path/to/client-cert.pem
|
||||
ClientKeyPath string `koanf:"client_key_path" json:"client_key_path"` // /path/to/client-key.pem
|
||||
}
|
||||
|
||||
type CacheConf struct {
|
||||
@@ -454,4 +458,18 @@ type AuthConf struct {
|
||||
ClientSecret string `koanf:"client_secret" json:"client_secret"`
|
||||
Domain string `koanf:"domain" json:"domain"`
|
||||
} `koanf:"auth0" json:"auth0"`
|
||||
|
||||
// Token exchange — accept an access token from an external OIDC IdP
|
||||
// (verified via that IdP's /userinfo) and issue an Artalk JWT for the
|
||||
// matching user. Lets a surrounding application that already runs OIDC
|
||||
// (e.g. an Auth0 SPA) drive Artalk sign-in without showing Artalk's
|
||||
// own popup login.
|
||||
SSO AuthSSOConf `koanf:"sso" json:"sso"`
|
||||
}
|
||||
|
||||
type AuthSSOConf struct {
|
||||
Enabled bool `koanf:"enabled" json:"enabled"`
|
||||
// Issuer is the OIDC issuer URL — e.g. "tenant.auth0.com" or
|
||||
// "https://tenant.auth0.com". /userinfo is called against this host.
|
||||
Issuer string `koanf:"issuer" json:"issuer"`
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"path"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -69,7 +70,7 @@ func main() {
|
||||
case "json":
|
||||
result = getJSON(metas)
|
||||
case "markdown":
|
||||
result = getMarkdown(metas)
|
||||
result = getMarkdown(metas, outputFile)
|
||||
default:
|
||||
panic("invalid output format")
|
||||
}
|
||||
@@ -101,13 +102,45 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func formatMap(m map[string]string) string {
|
||||
// To ensure consistent output, we'll sort the map keys.
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
str := fmt.Sprintf("%T{\n", m)
|
||||
for _, k := range keys {
|
||||
str += fmt.Sprintf("\t%#v: %#v,\n", k, m[k])
|
||||
}
|
||||
str += "}"
|
||||
return str
|
||||
}
|
||||
|
||||
func formatOptionsMetaSlice(s []meta.OptionsMeta) string {
|
||||
// To ensure consistent output, we'll sort by meta.OptionsMeta.Env
|
||||
sorted := make([]meta.OptionsMeta, len(s))
|
||||
copy(sorted, s)
|
||||
sort.Slice(sorted, func(i, j int) bool {
|
||||
return sorted[i].Env < sorted[j].Env
|
||||
})
|
||||
|
||||
str := fmt.Sprintf("%T{\n", s)
|
||||
for _, item := range sorted {
|
||||
str += fmt.Sprintf("\t%#v,\n", item)
|
||||
}
|
||||
str += "}"
|
||||
return str
|
||||
}
|
||||
|
||||
func getGo(metas []meta.OptionsMeta, mapping map[string]string) string {
|
||||
str := "// Code generated by go run internal/config/meta/gen/main.go. DO NOT EDIT.\n\n"
|
||||
str += "package " + pkgName + "\n\nimport \"github.com/artalkjs/artalk/v2/internal/config/meta\"\n\n"
|
||||
str += "// Cache result of `meta.GetEnvPathMap(config.Config{}, config.Template(\"" + locale + "\"))`\n"
|
||||
str += fmt.Sprintf("var EnvPathMapCache = %#v\n\n", mapping)
|
||||
str += "var EnvPathMapCache = " + formatMap(mapping) + "\n\n"
|
||||
str += "// Cache result of `meta.GetOptionsMetaData(config.Template(\"" + locale + "\"))`\n"
|
||||
str += fmt.Sprintf("var OptionsMetaCache = %#v\n", metas)
|
||||
str += "var OptionsMetaCache = " + formatOptionsMetaSlice(metas) + "\n"
|
||||
return str
|
||||
}
|
||||
|
||||
@@ -119,14 +152,21 @@ func getJSON(metas []meta.OptionsMeta) string {
|
||||
return string(str)
|
||||
}
|
||||
|
||||
func getMarkdown(metas []meta.OptionsMeta) string {
|
||||
func getMarkdown(metas []meta.OptionsMeta, templateFile string) string {
|
||||
generalTableTitle, tableFields := "通用配置", []string{"环境变量", "默认值", "描述", "路径"}
|
||||
|
||||
if templateFile != "" {
|
||||
if _, err := os.Stat(templateFile); err == nil {
|
||||
_, _, _, generalTableTitle, tableFields = parseMarkdownEnv(templateFile)
|
||||
}
|
||||
}
|
||||
md := ""
|
||||
|
||||
// Get root nodes
|
||||
rootNodes := []meta.OptionsMeta{
|
||||
{
|
||||
Title: "通用配置",
|
||||
Desc: "通用配置项",
|
||||
Title: generalTableTitle,
|
||||
Desc: generalTableTitle,
|
||||
Path: "general",
|
||||
IsRoot: true,
|
||||
},
|
||||
@@ -137,7 +177,7 @@ func getMarkdown(metas []meta.OptionsMeta) string {
|
||||
|
||||
printGroup := func(root meta.OptionsMeta, items []meta.OptionsMeta) {
|
||||
md += fmt.Sprintf("## %s\n\n", root.Title)
|
||||
md += fmt.Sprintln("| 环境变量 | 默认值 | 描述 | 路径 |")
|
||||
md += "| " + strings.Join(tableFields, " | ") + " |\n"
|
||||
md += fmt.Sprintln("| --- | --- | --- | --- |")
|
||||
for _, m := range items {
|
||||
if m.Title == "Enabled" {
|
||||
@@ -169,6 +209,103 @@ func getMarkdown(metas []meta.OptionsMeta) string {
|
||||
return strings.TrimSpace(md)
|
||||
}
|
||||
|
||||
var start = "<!-- env-variables -->"
|
||||
var end = "<!-- /env-variables -->"
|
||||
// parseMarkdownEnv extracts the env.md to be:
|
||||
// - header: content before the env variables section
|
||||
// - body: the content between start and end markers
|
||||
// - firstTableTitle: the first title (## ...) after the start marker
|
||||
// - tableFields: the first table header row after the first table title
|
||||
// - footer: content after the env variables section
|
||||
//
|
||||
// For example, given a snippet from env.md:
|
||||
// ```md
|
||||
// ...header...
|
||||
// <!-- env-variables -->
|
||||
// ## 通用配置
|
||||
//
|
||||
// | 环境变量 | 默认值 | 描述 | 路径 |
|
||||
// | --- | --- | --- | --- |
|
||||
// | **ATK_ADMIN_USERS** | `[]` | 管理员账户 | admin_users (管理员账户) |
|
||||
// ...more tables...
|
||||
// <!-- /env-variables -->
|
||||
// ...footer...
|
||||
// ```
|
||||
// The function should return:
|
||||
// header: "...header...\n"
|
||||
// body: "\n## 通用配置\n\n| 环境变量 | 默认值 | 描述 | 路径 |\n| --- | --- | --- | --- |\n| **ATK_ADMIN_USERS** | `[]` | 管理员账户 | admin_users (管理员账户) |\n...more tables...\n"
|
||||
// footer: "\n...footer..."
|
||||
// firstTableTitle: "通用配置"
|
||||
// tableFields: ["环境变量", "默认值", "描述", "路径"]
|
||||
func parseMarkdownEnv(file string) (header, body, footer, firstTableTitle string, tableFields []string) {
|
||||
contentBytes, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
content := string(contentBytes)
|
||||
|
||||
startIdx := strings.Index(content, start)
|
||||
endIdx := strings.Index(content, end)
|
||||
if startIdx == -1 || endIdx == -1 || endIdx < startIdx {
|
||||
panic(fmt.Sprintf("%s markers not found or invalid", start))
|
||||
}
|
||||
|
||||
header = content[:startIdx]
|
||||
body = content[startIdx+len(start) : endIdx]
|
||||
footer = content[endIdx+len(end):]
|
||||
|
||||
// Find the first title (## ...) after the start marker
|
||||
afterStart := content[startIdx+len(start):]
|
||||
titleIdx := strings.Index(afterStart, "## ")
|
||||
if titleIdx == -1 {
|
||||
firstTableTitle = ""
|
||||
} else {
|
||||
// Find the end of the line
|
||||
titleLineEnd := strings.Index(afterStart[titleIdx:], "\n")
|
||||
if titleLineEnd == -1 {
|
||||
firstTableTitle = strings.TrimSpace(afterStart[titleIdx+3:])
|
||||
} else {
|
||||
firstTableTitle = strings.TrimSpace(afterStart[titleIdx+3 : titleIdx+titleLineEnd])
|
||||
}
|
||||
}
|
||||
|
||||
// Find the first table header after the general table title
|
||||
tableHeaderIdx := -1
|
||||
if titleIdx != -1 {
|
||||
afterTitle := afterStart[titleIdx:]
|
||||
tableHeaderIdx = strings.Index(afterTitle, "|")
|
||||
if tableHeaderIdx != -1 {
|
||||
// The first line starting with '|' is the table header
|
||||
tableHeaderLineEnd := strings.Index(afterTitle[tableHeaderIdx:], "\n")
|
||||
var tableHeaderLine string
|
||||
if tableHeaderLineEnd == -1 {
|
||||
tableHeaderLine = afterTitle[tableHeaderIdx:]
|
||||
} else {
|
||||
tableHeaderLine = afterTitle[tableHeaderIdx : tableHeaderIdx+tableHeaderLineEnd]
|
||||
}
|
||||
// Split and trim fields
|
||||
fields := strings.Split(tableHeaderLine, "|")
|
||||
for _, f := range fields {
|
||||
f = strings.TrimSpace(f)
|
||||
if f != "" {
|
||||
tableFields = append(tableFields, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove empty fields at the start/end (from leading/trailing '|')
|
||||
if len(tableFields) > 0 && tableFields[0] == "" {
|
||||
tableFields = tableFields[1:]
|
||||
}
|
||||
if len(tableFields) > 0 && tableFields[len(tableFields)-1] == "" {
|
||||
tableFields = tableFields[:len(tableFields)-1]
|
||||
}
|
||||
|
||||
body = strings.Trim(body, "\n")
|
||||
return
|
||||
}
|
||||
|
||||
func writeMarkdown(file string, text string) {
|
||||
// read file content
|
||||
content, err := os.ReadFile(file)
|
||||
@@ -176,13 +313,10 @@ func writeMarkdown(file string, text string) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// find `<!-- env-variables --><!-- /env-variables -->`
|
||||
start := "<!-- env-variables -->"
|
||||
end := "<!-- /env-variables -->"
|
||||
re := regexp.MustCompile(start + `(?s:(.*?))` + end)
|
||||
matches := re.FindAllStringSubmatch(string(content), -1)
|
||||
if len(matches) == 0 {
|
||||
panic("<!-- env-variables --> not found")
|
||||
panic(fmt.Sprintf("%s not found", start))
|
||||
}
|
||||
|
||||
// replace
|
||||
|
||||
@@ -9,13 +9,24 @@ import (
|
||||
// The version of Artalk
|
||||
//
|
||||
// Which is automatically set by the CI release workflow
|
||||
const Version = "v2.9.1"
|
||||
var Version = "v2.10.0"
|
||||
|
||||
// buildCommitHash is optionally set by build scripts when the VCS metadata is
|
||||
// not available in the build context (for example, inside a Docker build).
|
||||
var buildCommitHash string
|
||||
|
||||
// The commit hash from which the binary was built (optional)
|
||||
//
|
||||
// This value is set by the build script:
|
||||
// `go build -ldflags "-X 'github.com/artalkjs/artalk/v2/internal/config.CommitHash=$(git rev-parse --short HEAD)'"`
|
||||
func CommitHash() string {
|
||||
if buildCommitHash != "" {
|
||||
if len(buildCommitHash) >= 7 {
|
||||
return buildCommitHash[:7]
|
||||
}
|
||||
return buildCommitHash
|
||||
}
|
||||
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return ""
|
||||
|
||||
+41
-8
@@ -58,6 +58,42 @@ func (app *App) registerDefaultHooks() {
|
||||
|
||||
var mutex = sync.Mutex{}
|
||||
|
||||
type timezoneInitializer struct {
|
||||
once sync.Once
|
||||
name string
|
||||
err error
|
||||
}
|
||||
|
||||
func (i *timezoneInitializer) init(timezone string, apply func(*time.Location)) error {
|
||||
if timezone == "" {
|
||||
timezone = "Local"
|
||||
}
|
||||
|
||||
i.once.Do(func() {
|
||||
i.name = timezone
|
||||
if timezone == "Local" {
|
||||
return
|
||||
}
|
||||
|
||||
local, err := time.LoadLocation(timezone)
|
||||
if err != nil {
|
||||
i.err = fmt.Errorf("timezone load error: %w (please check config or system env)", err)
|
||||
return
|
||||
}
|
||||
apply(local)
|
||||
})
|
||||
|
||||
if i.err != nil {
|
||||
return i.err
|
||||
}
|
||||
if i.name != timezone {
|
||||
return fmt.Errorf("timezone is already initialized as %q; restart the process to change it to %q", i.name, timezone)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var processTimezone timezoneInitializer
|
||||
|
||||
// Bootstrap implements App.
|
||||
func (app *App) Bootstrap() error {
|
||||
mutex.Lock()
|
||||
@@ -67,14 +103,11 @@ func (app *App) Bootstrap() error {
|
||||
return fmt.Errorf("app.conf cannot be nil while bootstrap")
|
||||
}
|
||||
|
||||
// 时区设置
|
||||
timezone := app.Conf().TimeZone
|
||||
if timezone != "" {
|
||||
if local, err := time.LoadLocation(timezone); err == nil {
|
||||
time.Local = local
|
||||
} else {
|
||||
return fmt.Errorf("timezone load error: %w (please check config or system env)", err)
|
||||
}
|
||||
// time.Local is process-global and must not be mutated after services start.
|
||||
if err := processTimezone.init(app.Conf().TimeZone, func(local *time.Location) {
|
||||
time.Local = local
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// i18n
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/artalkjs/artalk/v2/internal/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type MockService struct{}
|
||||
@@ -23,6 +27,44 @@ func TestNewApp(t *testing.T) {
|
||||
assert.NotNil(t, app.service)
|
||||
}
|
||||
|
||||
func TestTimezoneInitializer(t *testing.T) {
|
||||
var initializer timezoneInitializer
|
||||
var applyCount atomic.Int32
|
||||
apply := func(location *time.Location) {
|
||||
assert.Equal(t, time.UTC, location)
|
||||
applyCount.Add(1)
|
||||
}
|
||||
|
||||
const callers = 32
|
||||
errs := make(chan error, callers)
|
||||
var wg sync.WaitGroup
|
||||
for range callers {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
errs <- initializer.init("UTC", apply)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
close(errs)
|
||||
for err := range errs {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, int32(1), applyCount.Load())
|
||||
|
||||
err := initializer.init("Asia/Shanghai", apply)
|
||||
assert.ErrorContains(t, err, "restart the process")
|
||||
assert.Equal(t, int32(1), applyCount.Load())
|
||||
|
||||
var invalid timezoneInitializer
|
||||
err = invalid.init("Invalid/Timezone", apply)
|
||||
assert.ErrorContains(t, err, "timezone load error")
|
||||
|
||||
var local timezoneInitializer
|
||||
require.NoError(t, local.init("Local", apply))
|
||||
assert.ErrorContains(t, local.init("UTC", apply), "restart the process")
|
||||
}
|
||||
|
||||
func TestAppBootstrap(t *testing.T) {
|
||||
conf := &config.Config{
|
||||
Cache: config.CacheConf{
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ func NewDB(conf config.DBConf) (*gorm.DB, error) {
|
||||
case config.TypeSQLite:
|
||||
return OpenSQLite(dsn, gormConfig)
|
||||
case config.TypeMySql:
|
||||
return OpenMySql(dsn, gormConfig)
|
||||
return OpenMySql(dsn, gormConfig, &conf)
|
||||
case config.TypePostgreSQL:
|
||||
return OpenPostgreSQL(dsn, gormConfig)
|
||||
case config.TypeMSSQL:
|
||||
|
||||
+5
-1
@@ -25,6 +25,10 @@ func getDsnByConf(conf config.DBConf) string {
|
||||
)
|
||||
|
||||
case config.TypeMySql:
|
||||
tlsMode := lo.If(conf.SSL, "true").Else("false")
|
||||
if conf.ServerCaPath != "" && conf.ClientCertPath != "" && conf.ClientKeyPath != "" {
|
||||
tlsMode = mysqlCloudSQLTLSConfigName
|
||||
}
|
||||
dsn = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=Local&tls=%s",
|
||||
conf.User,
|
||||
conf.Password,
|
||||
@@ -32,7 +36,7 @@ func getDsnByConf(conf config.DBConf) string {
|
||||
conf.Port,
|
||||
conf.Name,
|
||||
conf.Charset,
|
||||
lo.If(conf.SSL, "true").Else("false"),
|
||||
tlsMode,
|
||||
)
|
||||
|
||||
case config.TypeMSSQL:
|
||||
|
||||
@@ -47,6 +47,22 @@ func TestGetDsnByConf(t *testing.T) {
|
||||
},
|
||||
expected: "user:password@tcp(localhost:3306)/dbname?charset=utf8&parseTime=True&loc=Local&tls=false",
|
||||
},
|
||||
{
|
||||
name: "MySQL Cloud SQL TLS configuration",
|
||||
conf: config.DBConf{
|
||||
Type: config.TypeMySql,
|
||||
Host: "db.example.internal",
|
||||
User: "user",
|
||||
Password: "password",
|
||||
Name: "dbname",
|
||||
Port: 3306,
|
||||
Charset: "utf8",
|
||||
ServerCaPath: "server-ca.pem",
|
||||
ClientCertPath: "client-cert.pem",
|
||||
ClientKeyPath: "client-key.pem",
|
||||
},
|
||||
expected: "user:password@tcp(db.example.internal:3306)/dbname?charset=utf8&parseTime=True&loc=Local&tls=cloudsql",
|
||||
},
|
||||
{
|
||||
name: "SQL Server configuration",
|
||||
conf: config.DBConf{
|
||||
|
||||
+55
-2
@@ -1,17 +1,24 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/artalkjs/artalk/v2/internal/config"
|
||||
"github.com/artalkjs/artalk/v2/internal/utils"
|
||||
gomysql "github.com/go-sql-driver/mysql"
|
||||
"github.com/libtnb/sqlite"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/driver/sqlserver"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const mysqlCloudSQLTLSConfigName = "cloudsql"
|
||||
|
||||
func OpenSQLite(filename string, gormConfig *gorm.Config) (*gorm.DB, error) {
|
||||
if filename == "" {
|
||||
return nil, fmt.Errorf("please set `db.file` option in config file to specify a sqlite database path")
|
||||
@@ -22,10 +29,56 @@ func OpenSQLite(filename string, gormConfig *gorm.Config) (*gorm.DB, error) {
|
||||
return gorm.Open(sqlite.Open(filename), gormConfig)
|
||||
}
|
||||
|
||||
func OpenMySql(dsn string, gormConfig *gorm.Config) (*gorm.DB, error) {
|
||||
func OpenMySql(dsn string, gormConfig *gorm.Config, dbConf *config.DBConf) (*gorm.DB, error) {
|
||||
tlsConfig, configured, err := loadMySQLTLSConfig(dbConf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if configured {
|
||||
if err := gomysql.RegisterTLSConfig(mysqlCloudSQLTLSConfigName, tlsConfig); err != nil {
|
||||
return nil, fmt.Errorf("failed to register MySQL TLS config: %w", err)
|
||||
}
|
||||
}
|
||||
return gorm.Open(mysql.Open(dsn), gormConfig)
|
||||
}
|
||||
|
||||
func loadMySQLTLSConfig(dbConf *config.DBConf) (*tls.Config, bool, error) {
|
||||
paths := []string{dbConf.ServerCaPath, dbConf.ClientCertPath, dbConf.ClientKeyPath}
|
||||
configuredPaths := 0
|
||||
for _, path := range paths {
|
||||
if path != "" {
|
||||
configuredPaths++
|
||||
}
|
||||
}
|
||||
if configuredPaths == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
if configuredPaths != len(paths) {
|
||||
return nil, false, fmt.Errorf("server CA, client certificate, and client key must be configured together")
|
||||
}
|
||||
|
||||
serverCertPool := x509.NewCertPool()
|
||||
pem, err := os.ReadFile(dbConf.ServerCaPath)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("failed to read server CA file: %w", err)
|
||||
}
|
||||
if ok := serverCertPool.AppendCertsFromPEM(pem); !ok {
|
||||
return nil, false, fmt.Errorf("unable to append root cert to pool")
|
||||
}
|
||||
|
||||
clientCert, err := tls.LoadX509KeyPair(dbConf.ClientCertPath, dbConf.ClientKeyPath)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("failed to load client certificate/key: %w", err)
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
RootCAs: serverCertPool,
|
||||
Certificates: []tls.Certificate{clientCert},
|
||||
ServerName: dbConf.Host,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
func OpenPostgreSQL(dsn string, gormConfig *gorm.Config) (*gorm.DB, error) {
|
||||
return gorm.Open(postgres.New(postgres.Config{
|
||||
DSN: dsn,
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/artalkjs/artalk/v2/internal/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLoadMySQLTLSConfig(t *testing.T) {
|
||||
certDir := t.TempDir()
|
||||
caCert, caKey := createTestCertificate(t, nil, nil, x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{CommonName: "Test CA"},
|
||||
NotBefore: time.Now().Add(-time.Hour),
|
||||
NotAfter: time.Now().Add(time.Hour),
|
||||
IsCA: true,
|
||||
BasicConstraintsValid: true,
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
|
||||
})
|
||||
serverCert, _ := createTestCertificate(t, caCert, caKey, x509.Certificate{
|
||||
SerialNumber: big.NewInt(2),
|
||||
Subject: pkix.Name{CommonName: "db.example.internal"},
|
||||
DNSNames: []string{"db.example.internal"},
|
||||
NotBefore: time.Now().Add(-time.Hour),
|
||||
NotAfter: time.Now().Add(time.Hour),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
})
|
||||
clientCert, clientKey := createTestCertificate(t, caCert, caKey, x509.Certificate{
|
||||
SerialNumber: big.NewInt(3),
|
||||
Subject: pkix.Name{CommonName: "client"},
|
||||
NotBefore: time.Now().Add(-time.Hour),
|
||||
NotAfter: time.Now().Add(time.Hour),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
})
|
||||
|
||||
caPath := writeTestCertificate(t, certDir, "server-ca.pem", caCert.Raw)
|
||||
clientCertPath := writeTestCertificate(t, certDir, "client-cert.pem", clientCert.Raw)
|
||||
clientKeyPath := writeTestPrivateKey(t, certDir, "client-key.pem", clientKey)
|
||||
|
||||
tlsConfig, configured, err := loadMySQLTLSConfig(&config.DBConf{
|
||||
Host: "db.example.internal",
|
||||
ServerCaPath: caPath,
|
||||
ClientCertPath: clientCertPath,
|
||||
ClientKeyPath: clientKeyPath,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, configured)
|
||||
assert.False(t, tlsConfig.InsecureSkipVerify)
|
||||
assert.Equal(t, "db.example.internal", tlsConfig.ServerName)
|
||||
assert.Equal(t, uint16(tls.VersionTLS12), tlsConfig.MinVersion)
|
||||
assert.Len(t, tlsConfig.Certificates, 1)
|
||||
|
||||
_, err = serverCert.Verify(x509.VerifyOptions{
|
||||
DNSName: tlsConfig.ServerName,
|
||||
Roots: tlsConfig.RootCAs,
|
||||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = serverCert.Verify(x509.VerifyOptions{
|
||||
DNSName: "attacker.example.internal",
|
||||
Roots: tlsConfig.RootCAs,
|
||||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
})
|
||||
assert.Error(t, err, "A certificate for a different host must be rejected")
|
||||
}
|
||||
|
||||
func TestLoadMySQLTLSConfigRequiresCompleteCertificateSet(t *testing.T) {
|
||||
_, _, err := loadMySQLTLSConfig(&config.DBConf{ServerCaPath: "server-ca.pem"})
|
||||
assert.EqualError(t, err, "server CA, client certificate, and client key must be configured together")
|
||||
}
|
||||
|
||||
func createTestCertificate(
|
||||
t *testing.T,
|
||||
parent *x509.Certificate,
|
||||
parentKey ed25519.PrivateKey,
|
||||
template x509.Certificate,
|
||||
) (*x509.Certificate, ed25519.PrivateKey) {
|
||||
t.Helper()
|
||||
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
require.NoError(t, err)
|
||||
if parent == nil {
|
||||
parent = &template
|
||||
parentKey = privateKey
|
||||
}
|
||||
der, err := x509.CreateCertificate(rand.Reader, &template, parent, publicKey, parentKey)
|
||||
require.NoError(t, err)
|
||||
cert, err := x509.ParseCertificate(der)
|
||||
require.NoError(t, err)
|
||||
return cert, privateKey
|
||||
}
|
||||
|
||||
func writeTestCertificate(t *testing.T, dir string, name string, der []byte) string {
|
||||
t.Helper()
|
||||
path := filepath.Join(dir, name)
|
||||
require.NoError(t, os.WriteFile(path, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), 0o600))
|
||||
return path
|
||||
}
|
||||
|
||||
func writeTestPrivateKey(t *testing.T, dir string, name string, key ed25519.PrivateKey) string {
|
||||
t.Helper()
|
||||
der, err := x509.MarshalPKCS8PrivateKey(key)
|
||||
require.NoError(t, err)
|
||||
path := filepath.Join(dir, name)
|
||||
require.NoError(t, os.WriteFile(path, pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: der}), 0o600))
|
||||
return path
|
||||
}
|
||||
@@ -18,7 +18,7 @@ func TestQueue(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("Send", func(t *testing.T) {
|
||||
sendSuccessHookTriggered := false
|
||||
sendSuccess := make(chan Email, 1)
|
||||
|
||||
q := NewQueue(EmailConf{
|
||||
EmailConf: config.EmailConf{},
|
||||
@@ -26,37 +26,53 @@ func TestQueue(t *testing.T) {
|
||||
SendResult: true,
|
||||
},
|
||||
OnSendSuccess: func(email *Email) {
|
||||
assert.Equal(t, testEmail, *email)
|
||||
sendSuccessHookTriggered = true
|
||||
sendSuccess <- *email
|
||||
},
|
||||
})
|
||||
defer q.Close()
|
||||
|
||||
q.Push(&testEmail)
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
assert.True(t, sendSuccessHookTriggered, "Send success hook should be triggered")
|
||||
select {
|
||||
case email := <-sendSuccess:
|
||||
assert.Equal(t, testEmail, email)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("Send success hook should be triggered")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Send failed", func(t *testing.T) {
|
||||
sendSuccessHookTriggered := false
|
||||
sendAttempted := make(chan Email, 1)
|
||||
sendSuccess := make(chan Email, 1)
|
||||
|
||||
q := NewQueue(EmailConf{
|
||||
EmailConf: config.EmailConf{},
|
||||
Sender: &mockSender{
|
||||
SendResult: false,
|
||||
OnSend: func(email *Email) {
|
||||
sendAttempted <- *email
|
||||
},
|
||||
},
|
||||
OnSendSuccess: func(email *Email) {
|
||||
assert.Equal(t, testEmail, email)
|
||||
sendSuccessHookTriggered = true
|
||||
sendSuccess <- *email
|
||||
},
|
||||
})
|
||||
defer q.Close()
|
||||
|
||||
q.Push(&testEmail)
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
select {
|
||||
case email := <-sendAttempted:
|
||||
assert.Equal(t, testEmail, email)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("Email send should be attempted")
|
||||
}
|
||||
|
||||
assert.False(t, sendSuccessHookTriggered, "Send success hook should not be triggered")
|
||||
select {
|
||||
case <-sendSuccess:
|
||||
t.Fatal("Send success hook should not be triggered")
|
||||
default:
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Close", func(t *testing.T) {
|
||||
@@ -69,8 +85,6 @@ func TestQueue(t *testing.T) {
|
||||
|
||||
q.Close()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
assert.True(t, q.closed, "Queue should be closed")
|
||||
})
|
||||
}
|
||||
@@ -81,10 +95,14 @@ func TestQueue(t *testing.T) {
|
||||
|
||||
type mockSender struct {
|
||||
SendResult bool
|
||||
OnSend func(email *Email)
|
||||
}
|
||||
|
||||
var _ Sender = (*mockSender)(nil)
|
||||
|
||||
func (s *mockSender) Send(email *Email) bool {
|
||||
if s.OnSend != nil {
|
||||
s.OnSend(email)
|
||||
}
|
||||
return s.SendResult
|
||||
}
|
||||
@@ -2,10 +2,15 @@ package email
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/mail"
|
||||
"strings"
|
||||
|
||||
"github.com/artalkjs/artalk/v2/internal/utils"
|
||||
"gopkg.in/gomail.v2"
|
||||
)
|
||||
|
||||
const fallbackMessageIDDomain = "artalk.local"
|
||||
|
||||
func getCookedEmail(email *Email) *gomail.Message {
|
||||
m := gomail.NewMessage()
|
||||
|
||||
@@ -17,6 +22,8 @@ func getCookedEmail(email *Email) *gomail.Message {
|
||||
//m.SetAddressHeader("Cc", "dan@example.com", "Dan")
|
||||
// 主题
|
||||
m.SetHeader("Subject", email.Subject)
|
||||
// 唯一邮件标识
|
||||
m.SetHeader("Message-ID", newMessageID(email.FromAddr))
|
||||
// 内容
|
||||
m.SetBody("text/html", email.Body)
|
||||
// 附件
|
||||
@@ -25,6 +32,17 @@ func getCookedEmail(email *Email) *gomail.Message {
|
||||
return m
|
||||
}
|
||||
|
||||
func newMessageID(fromAddr string) string {
|
||||
domain := fallbackMessageIDDomain
|
||||
if addr, err := mail.ParseAddress(fromAddr); err == nil {
|
||||
if i := strings.LastIndexByte(addr.Address, '@'); i >= 0 && i < len(addr.Address)-1 {
|
||||
domain = addr.Address[i+1:]
|
||||
}
|
||||
}
|
||||
|
||||
return "<" + utils.RandomString(32) + "@" + domain + ">"
|
||||
}
|
||||
|
||||
func getEmailMineTxt(email *Email) string {
|
||||
emailBuffer := bytes.NewBuffer([]byte{})
|
||||
getCookedEmail(email).WriteTo(emailBuffer)
|
||||
|
||||
Loaded 100 of 198 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user