Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d95baa70f | ||
|
|
050bce163a | ||
|
|
68b84683d0 | ||
|
|
6fe373d540 | ||
|
|
ba19bcc729 | ||
|
|
f1b65db2c7 | ||
|
|
f5f287ef06 | ||
|
|
f161e6033c | ||
|
|
c86bff8811 | ||
|
|
596b1045bd | ||
|
|
907ca2b590 | ||
|
|
f1b5469b83 | ||
|
|
984fbde540 | ||
|
|
2bdc05dd47 | ||
|
|
d2ea572abf | ||
|
|
8d5c4cd578 | ||
|
|
dd1ba9b518 | ||
|
|
cebe929118 | ||
|
|
3eccf01f1f | ||
|
|
050f1b7941 | ||
|
|
53cc370727 | ||
|
|
363e05d580 | ||
|
|
2ecc80e18c | ||
|
|
0728b28856 | ||
|
|
a6a9270f63 | ||
|
|
372429a9f5 | ||
|
|
4733ad8eb1 | ||
|
|
8e96aa26b4 | ||
|
|
c0e8520d31 | ||
|
|
345f80d90d | ||
|
|
ffcef2fe99 | ||
|
|
e77dc33333 | ||
|
|
5fee19f6c7 | ||
|
|
659c623240 | ||
|
|
695d0ea13a | ||
|
|
0f7ac514fb | ||
|
|
9ad3be2e97 | ||
|
|
2c36fab8aa | ||
|
|
3b7a4b6e52 | ||
|
|
499302c48e | ||
|
|
28fbe76547 | ||
|
|
fabb31275d | ||
|
|
96b75af027 | ||
|
|
dc048ef047 | ||
|
|
d6b2960a4a | ||
|
|
b47b6c4f56 | ||
|
|
75427df7de | ||
|
|
cb98885e1f | ||
|
|
7e0445cc94 | ||
|
|
a9836aaea0 | ||
|
|
27b28dba0d | ||
|
|
86d059bf99 | ||
|
|
76f5ce32ca | ||
|
|
ff9eab998a | ||
|
|
ac3a36eb13 | ||
|
|
4b4c749756 | ||
|
|
4777f4059f | ||
|
|
ad5d555ac8 | ||
|
|
fc7540fc9b | ||
|
|
63a2bdea48 | ||
|
|
41d47fdb3e | ||
|
|
86dae37c5d | ||
|
|
2a97d9379e | ||
|
|
ba0060316c | ||
|
|
41f18a23e5 | ||
|
|
bf26ad4acc | ||
|
|
acc56ad42b | ||
|
|
922e779118 | ||
|
|
f104a6e1b6 | ||
|
|
0aa6052eba | ||
|
|
f25b34d5e2 | ||
|
|
5e5b3e0830 | ||
|
|
f1163139d7 | ||
|
|
5db6e4364a | ||
|
|
3d1a3fd7cf | ||
|
|
6625face50 | ||
|
|
d9764c251d | ||
|
|
243c8356e7 | ||
|
|
2d2f2ab02a | ||
|
|
50785e0577 | ||
|
|
9c1a827685 | ||
|
|
8c658b7eda | ||
|
|
26d8d3daee | ||
|
|
26476db95d | ||
|
|
3c90f6ae61 | ||
|
|
4889afdf0c | ||
|
|
2e777ea752 | ||
|
|
fddf1e21f3 | ||
|
|
d6013b10e6 | ||
|
|
aac6af40cc | ||
|
|
1f96a0e4d3 | ||
|
|
99716984ad | ||
|
|
24e9404a6f | ||
|
|
1f1adba5fd | ||
|
|
b907354746 | ||
|
|
9a08d4a412 | ||
|
|
3a08e6dd55 | ||
|
|
02f782a5ab | ||
|
|
30d68b2d1e | ||
|
|
8974cde582 | ||
|
|
5f3206a4e8 | ||
|
|
40e38d225e | ||
|
|
a73072c8fb | ||
|
|
6a5c5a4c08 | ||
|
|
fa7d5cee87 | ||
|
|
fe4db30e6d | ||
|
|
6936268fd2 | ||
|
|
e55f6ffdf3 | ||
|
|
e182e3c776 | ||
|
|
6309443d1f | ||
|
|
6f81bf00e8 | ||
|
|
86a1f5ee5d | ||
|
|
b3e460eebd | ||
|
|
5121c48c31 | ||
|
|
5f8e16cbe2 | ||
|
|
12e4f283fe | ||
|
|
20ca0896a6 |
+16
-3
@@ -1,14 +1,27 @@
|
|||||||
/logs/
|
/logs/
|
||||||
/target/
|
/target/
|
||||||
/var/
|
/var/
|
||||||
/frontend/node_modules/
|
|
||||||
/frontend/public/
|
|
||||||
/.vscode/
|
/.vscode/
|
||||||
/.idea/
|
/.idea/
|
||||||
/bin/
|
/bin/
|
||||||
|
/.git/
|
||||||
|
|
||||||
|
# frontend files not needed in docker image
|
||||||
|
/frontend/node_modules/
|
||||||
|
/frontend/apps/remark42/node_modules/
|
||||||
|
/frontend/apps/remark42/public/
|
||||||
|
# e2e tests arficats
|
||||||
|
/frontend/e2e/playwright-report/
|
||||||
|
/frontend/e2e/playwright/.cache/
|
||||||
|
/frontend/e2e/test-results/
|
||||||
|
|
||||||
# source files
|
# source files
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
|
compose-dev-backend.yml
|
||||||
|
compose-dev-frontend.yml
|
||||||
|
compose-private-backend.yml
|
||||||
|
compose-private-frontend.yml
|
||||||
|
compose-private.yml
|
||||||
rest-client.env.json
|
rest-client.env.json
|
||||||
|
|
||||||
# generated files
|
# generated files
|
||||||
@@ -21,4 +34,4 @@ debug.test
|
|||||||
*.test
|
*.test
|
||||||
remark42
|
remark42
|
||||||
/backend/var/
|
/backend/var/
|
||||||
compose-private-backend.yml
|
/playwright-report/
|
||||||
|
|||||||
+2
-1
@@ -2,4 +2,5 @@
|
|||||||
# Unless a later match takes precedence, @umputun will be requested for
|
# Unless a later match takes precedence, @umputun will be requested for
|
||||||
# review when someone opens a pull request.
|
# review when someone opens a pull request.
|
||||||
|
|
||||||
* @umputun
|
* @umputun
|
||||||
|
frontend/* @akellbl4 @Mavrin
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: test_backend
|
name: backend
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -10,17 +10,19 @@ on:
|
|||||||
- "!backend/scripts/**"
|
- "!backend/scripts/**"
|
||||||
- "!**.md"
|
- "!**.md"
|
||||||
pull_request:
|
pull_request:
|
||||||
|
types: [opened, reopened]
|
||||||
paths:
|
paths:
|
||||||
- ".github/workflows/ci-test-backend.yml"
|
- ".github/workflows/ci-test-backend.yml"
|
||||||
- "backend/**"
|
- "backend/**"
|
||||||
- "!backend/scripts/**"
|
- "!backend/scripts/**"
|
||||||
- "!**.md"
|
- "!**.md"
|
||||||
jobs:
|
jobs:
|
||||||
test-backend:
|
test:
|
||||||
|
name: Test & Coverage
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: debug if needed
|
- name: debug if needed
|
||||||
run: if [[ "$DEBUG" == "true" ]]; then env; fi
|
run: if [[ "$DEBUG" == "true" ]]; then env; fi
|
||||||
@@ -28,14 +30,14 @@ jobs:
|
|||||||
DEBUG: ${{secrets.DEBUG}}
|
DEBUG: ${{secrets.DEBUG}}
|
||||||
|
|
||||||
- name: install go
|
- name: install go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.17
|
go-version: 1.17
|
||||||
|
|
||||||
- name: install golangci-lint and goveralls
|
- name: install golangci-lint and goveralls
|
||||||
run: |
|
run: |
|
||||||
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $GITHUB_WORKSPACE v1.44.0
|
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $GITHUB_WORKSPACE v1.49.0
|
||||||
go get -u github.com/mattn/goveralls
|
go install github.com/mattn/goveralls@latest
|
||||||
|
|
||||||
- name: test and lint backend
|
- name: test and lint backend
|
||||||
run: |
|
run: |
|
||||||
@@ -44,7 +46,6 @@ jobs:
|
|||||||
$GITHUB_WORKSPACE/golangci-lint --config ${GITHUB_WORKSPACE}/backend/.golangci.yml run --out-format=github-actions ./...
|
$GITHUB_WORKSPACE/golangci-lint --config ${GITHUB_WORKSPACE}/backend/.golangci.yml run --out-format=github-actions ./...
|
||||||
working-directory: backend/app
|
working-directory: backend/app
|
||||||
env:
|
env:
|
||||||
GOFLAGS: "-mod=vendor"
|
|
||||||
TZ: "America/Chicago"
|
TZ: "America/Chicago"
|
||||||
|
|
||||||
- name: test and lint examples
|
- name: test and lint examples
|
||||||
@@ -58,7 +59,7 @@ jobs:
|
|||||||
TZ: "America/Chicago"
|
TZ: "America/Chicago"
|
||||||
|
|
||||||
- name: submit coverage
|
- name: submit coverage
|
||||||
run: $(go env GOPATH)/bin/goveralls -service="github" -coverprofile=$GITHUB_WORKSPACE/profile.cov
|
run: goveralls -service="github" -coverprofile=$GITHUB_WORKSPACE/profile.cov
|
||||||
working-directory: backend
|
working-directory: backend
|
||||||
env:
|
env:
|
||||||
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -7,16 +7,17 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- ".github/workflows/ci-build.yml"
|
- ".github/workflows/ci-build.yml"
|
||||||
- "backend/**"
|
- "backend/**"
|
||||||
- "frontend/**"
|
- "frontend/apps/**"
|
||||||
- ".dockerignore"
|
- ".dockerignore"
|
||||||
- "docker-init.sh"
|
- "docker-init.sh"
|
||||||
- "Dockerfile"
|
- "Dockerfile"
|
||||||
- "!**.md"
|
- "!**.md"
|
||||||
|
- "!frontend/packages/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- ".github/workflows/ci-build.yml"
|
- ".github/workflows/ci-build.yml"
|
||||||
- "backend/**"
|
- "backend/**"
|
||||||
- "frontend/**"
|
- "frontend/apps/**"
|
||||||
- ".dockerignore"
|
- ".dockerignore"
|
||||||
- "docker-init.sh"
|
- "docker-init.sh"
|
||||||
- "Dockerfile"
|
- "Dockerfile"
|
||||||
@@ -24,21 +25,36 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-images:
|
build-images:
|
||||||
|
name: Build Docker images
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: set up QEMU
|
- name: set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|
||||||
- name: set up Docker Buildx
|
- name: set up Docker Buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
- name: available platforms
|
- name: available platforms
|
||||||
run: echo ${{ steps.buildx.outputs.platforms }}
|
run: echo ${{ steps.buildx.outputs.platforms }}
|
||||||
|
|
||||||
|
- name: build docker image without pushing (only outside master)
|
||||||
|
if: ${{ github.ref != 'refs/heads/master' }}
|
||||||
|
run: |
|
||||||
|
docker buildx build \
|
||||||
|
--build-arg SKIP_BACKEND_TEST=true --build-arg SKIP_FRONTEND_TEST=true \
|
||||||
|
--platform linux/amd64 .
|
||||||
|
|
||||||
|
- name: build example docker image without pushing (only outside master)
|
||||||
|
if: ${{ github.ref != 'refs/heads/master' }}
|
||||||
|
run: |
|
||||||
|
docker buildx build \
|
||||||
|
--build-arg SKIP_BACKEND_TEST=true --build-arg SKIP_FRONTEND_TEST=true \
|
||||||
|
--platform linux/amd64 -f backend/_example/memory_store/Dockerfile .
|
||||||
|
|
||||||
- name: build and deploy master image to ghcr.io and dockerhub
|
- name: build and deploy master image to ghcr.io and dockerhub
|
||||||
if: ${{ github.ref == 'refs/heads/master' }}
|
if: ${{ github.ref == 'refs/heads/master' }}
|
||||||
env:
|
env:
|
||||||
@@ -51,12 +67,12 @@ jobs:
|
|||||||
ref="$(echo ${GITHUB_REF} | cut -d'/' -f3)"
|
ref="$(echo ${GITHUB_REF} | cut -d'/' -f3)"
|
||||||
echo "GITHUB_REF=${GITHUB_REF}, GITHUB_SHA=${GITHUB_SHA}, GIT_BRANCH=${ref}"
|
echo "GITHUB_REF=${GITHUB_REF}, GITHUB_SHA=${GITHUB_SHA}, GIT_BRANCH=${ref}"
|
||||||
echo ${GITHUB_PACKAGE_TOKEN} | docker login ghcr.io -u ${USERNAME} --password-stdin
|
echo ${GITHUB_PACKAGE_TOKEN} | docker login ghcr.io -u ${USERNAME} --password-stdin
|
||||||
echo ${DOCKER_HUB_TOKEN} | docker login -u ${USERNAME} --password-stdin
|
echo ${DOCKER_HUB_TOKEN} | docker login -u umputun --password-stdin
|
||||||
docker buildx build --push \
|
docker buildx build --push \
|
||||||
--build-arg SKIP_BACKEND_TEST=true --build-arg SKIP_FRONTEND_TEST=true --build-arg CI=github \
|
--build-arg SKIP_BACKEND_TEST=true --build-arg SKIP_FRONTEND_TEST=true --build-arg CI=github \
|
||||||
--build-arg GITHUB_SHA=${GITHUB_SHA} --build-arg GIT_BRANCH=${ref} --build-arg GITHUB_REF=${GITHUB_REF} \
|
--build-arg GITHUB_SHA=${GITHUB_SHA} --build-arg GIT_BRANCH=${ref} --build-arg GITHUB_REF=${GITHUB_REF} \
|
||||||
--platform linux/amd64,linux/arm/v7,linux/arm64 \
|
--platform linux/amd64,linux/arm/v7,linux/arm64 \
|
||||||
-t ghcr.io/${USERNAME}/remark42:${ref} -t ${USERNAME}/remark42:${ref} .
|
-t ghcr.io/umputun/remark42:${ref} -t umputun/remark42:${ref} .
|
||||||
|
|
||||||
- name: deploy tagged (latest) to ghcr.io and dockerhub
|
- name: deploy tagged (latest) to ghcr.io and dockerhub
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
@@ -70,7 +86,7 @@ jobs:
|
|||||||
ref="$(echo ${GITHUB_REF} | cut -d'/' -f3)"
|
ref="$(echo ${GITHUB_REF} | cut -d'/' -f3)"
|
||||||
echo "GITHUB_REF=${GITHUB_REF}, GITHUB_SHA=${GITHUB_SHA}, GIT_BRANCH=${ref}"
|
echo "GITHUB_REF=${GITHUB_REF}, GITHUB_SHA=${GITHUB_SHA}, GIT_BRANCH=${ref}"
|
||||||
echo ${GITHUB_PACKAGE_TOKEN} | docker login ghcr.io -u ${USERNAME} --password-stdin
|
echo ${GITHUB_PACKAGE_TOKEN} | docker login ghcr.io -u ${USERNAME} --password-stdin
|
||||||
echo ${DOCKER_HUB_TOKEN} | docker login -u ${USERNAME} --password-stdin
|
echo ${DOCKER_HUB_TOKEN} | docker login -u umputun --password-stdin
|
||||||
docker buildx build --push \
|
docker buildx build --push \
|
||||||
--build-arg SKIP_BACKEND_TEST=true --build-arg SKIP_FRONTEND_TEST=true --build-arg CI=github \
|
--build-arg SKIP_BACKEND_TEST=true --build-arg SKIP_FRONTEND_TEST=true --build-arg CI=github \
|
||||||
--build-arg GITHUB_SHA=${GITHUB_SHA} --build-arg GIT_BRANCH=${ref} --build-arg GITHUB_REF=${GITHUB_REF} \
|
--build-arg GITHUB_SHA=${GITHUB_SHA} --build-arg GIT_BRANCH=${ref} --build-arg GITHUB_REF=${GITHUB_REF} \
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
name: "@remark42/api"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/ci-frontend-api.yml"
|
||||||
|
- "frontend/packages/**"
|
||||||
|
- "!**.md"
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/ci-frontend-api.yml"
|
||||||
|
- "frontend/packages/**"
|
||||||
|
- "!**.md"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
type-check:
|
||||||
|
name: Type check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node: [16.15.1]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node }}
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v2.0.1
|
||||||
|
id: pnpm-install
|
||||||
|
with:
|
||||||
|
version: 7
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Get pnpm store directory
|
||||||
|
id: pnpm-cache
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
||||||
|
|
||||||
|
- name: Setup pnpm cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
||||||
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm i
|
||||||
|
working-directory: ./frontend
|
||||||
|
|
||||||
|
- name: Run type check
|
||||||
|
run: pnpm type-check:api
|
||||||
|
working-directory: ./frontend
|
||||||
|
|
||||||
|
lint:
|
||||||
|
name: Lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node: [16.15.1]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node }}
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v2.0.1
|
||||||
|
id: pnpm-install
|
||||||
|
with:
|
||||||
|
version: 7
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Get pnpm store directory
|
||||||
|
id: pnpm-cache
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
||||||
|
|
||||||
|
- name: Setup pnpm cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
||||||
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm i
|
||||||
|
working-directory: ./frontend
|
||||||
|
|
||||||
|
- name: Run linters
|
||||||
|
run: pnpm lint:api
|
||||||
|
working-directory: ./frontend/
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Tests & Coverage
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node: [16.15.1]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node }}
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v2.0.1
|
||||||
|
id: pnpm-install
|
||||||
|
with:
|
||||||
|
version: 7
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Get pnpm store directory
|
||||||
|
id: pnpm-cache
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
||||||
|
|
||||||
|
- name: Setup pnpm cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
||||||
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm i
|
||||||
|
working-directory: ./frontend
|
||||||
|
|
||||||
|
- name: Test & Coverage
|
||||||
|
run: pnpm coverage:api
|
||||||
|
working-directory: ./frontend
|
||||||
|
|
||||||
|
- name: Submit coverage
|
||||||
|
run: ${{ github.workspace }}/frontend/apps/remark42/node_modules/.bin/codecov
|
||||||
|
env:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
name: frontend
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- ".github/workflows/ci-frontend-size-limit.yml"
|
|
||||||
- "frontend/**"
|
|
||||||
- "!**.md"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
size:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
CI_JOB_NUMBER: 1
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- uses: andresz1/size-limit-action@v1
|
|
||||||
with:
|
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
directory: frontend
|
|
||||||
@@ -3,105 +3,218 @@ name: frontend
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
tags:
|
- master
|
||||||
paths:
|
paths:
|
||||||
- ".github/workflows/ci-frontend.yml"
|
- ".github/workflows/ci-frontend.yml"
|
||||||
- "frontend/**"
|
- "frontend/apps/remark42/**"
|
||||||
- "!**.md"
|
- "!**.md"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- ".github/workflows/ci-frontend.yml"
|
- ".github/workflows/ci-frontend.yml"
|
||||||
- "frontend/**"
|
- "frontend/apps/remark42/**"
|
||||||
- "!**.md"
|
- "!**.md"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-translations:
|
translations-check:
|
||||||
|
name: Translations check
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node: [16.13.2]
|
node: [16.15.1]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- name: Checkout
|
||||||
- uses: actions/setup-node@v1
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version: ${{ matrix.node }}
|
||||||
|
|
||||||
- run: npm ci --loglevel warn
|
- name: Install pnpm
|
||||||
working-directory: ./frontend
|
uses: pnpm/action-setup@v2.0.1
|
||||||
|
id: pnpm-install
|
||||||
- uses: actions/cache@v2
|
|
||||||
with:
|
with:
|
||||||
path: ${{ github.workspace }}/frontend/node_modules/.cache
|
version: 7
|
||||||
key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
run_install: false
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-node-
|
|
||||||
|
|
||||||
- run: npm run check:translation
|
- name: Get pnpm store directory
|
||||||
|
id: pnpm-cache
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
||||||
|
|
||||||
|
- name: Setup pnpm cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
||||||
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm i
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
|
|
||||||
check-typescript:
|
- name: Translations check
|
||||||
|
run: pnpm translation-check
|
||||||
|
working-directory: ./frontend/apps/remark42
|
||||||
|
|
||||||
|
type-check:
|
||||||
|
name: Type check
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node: [16.13.2]
|
node: [16.15.1]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- name: Checkout
|
||||||
- uses: actions/setup-node@v1
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version: ${{ matrix.node }}
|
||||||
|
|
||||||
- run: npm ci --loglevel warn
|
- name: Install pnpm
|
||||||
working-directory: ./frontend
|
uses: pnpm/action-setup@v2.0.1
|
||||||
|
id: pnpm-install
|
||||||
- uses: actions/cache@v2
|
|
||||||
with:
|
with:
|
||||||
path: ${{ github.workspace }}/frontend/node_modules/.cache
|
version: 7
|
||||||
key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
run_install: false
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-node-
|
|
||||||
|
|
||||||
- run: npm run check:types
|
- name: Get pnpm store directory
|
||||||
|
id: pnpm-cache
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
||||||
|
|
||||||
|
- name: Setup pnpm cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
||||||
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm i
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
|
|
||||||
|
- name: Run type check
|
||||||
|
run: pnpm type-check
|
||||||
|
working-directory: ./frontend/apps/remark42
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
|
name: Eslint & Stylelint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node: [16.13.2]
|
node: [16.15.1]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- name: Checkout
|
||||||
- uses: actions/setup-node@v1
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version: ${{ matrix.node }}
|
||||||
|
|
||||||
- run: npm ci --loglevel warn
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v2.0.1
|
||||||
|
id: pnpm-install
|
||||||
|
with:
|
||||||
|
version: 7
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Get pnpm store directory
|
||||||
|
id: pnpm-cache
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
||||||
|
|
||||||
|
- name: Setup pnpm cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
||||||
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm i
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
|
|
||||||
- run: npm run lint
|
- name: Run linters
|
||||||
working-directory: ./frontend
|
run: pnpm lint
|
||||||
|
working-directory: ./frontend/apps/remark42
|
||||||
|
|
||||||
|
size-limit:
|
||||||
|
name: Size limit
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
env:
|
||||||
|
CI_JOB_NUMBER: 1
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v2.0.1
|
||||||
|
id: pnpm-install
|
||||||
|
with:
|
||||||
|
version: 7
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Check bundle size
|
||||||
|
uses: andresz1/size-limit-action@v1
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
directory: ./frontend/apps/remark42
|
||||||
|
|
||||||
test:
|
test:
|
||||||
|
name: Tests & Coverage
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node: [16.13.2]
|
node: [16.15.1]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- name: Checkout
|
||||||
- uses: actions/setup-node@v1
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version: ${{ matrix.node }}
|
||||||
|
|
||||||
- run: npm ci --loglevel warn
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v2.0.1
|
||||||
|
id: pnpm-install
|
||||||
|
with:
|
||||||
|
version: 7
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Get pnpm store directory
|
||||||
|
id: pnpm-cache
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
||||||
|
|
||||||
|
- name: Setup pnpm cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
||||||
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm i
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
|
|
||||||
- run: npm run test:coverage
|
- name: Test & Coverage
|
||||||
working-directory: ./frontend
|
run: pnpm coverage
|
||||||
|
working-directory: ./frontend/apps/remark42
|
||||||
|
|
||||||
- name: submit coverage
|
- name: Submit coverage
|
||||||
run: node ${{ github.workspace }}/frontend/node_modules/.bin/codecov
|
run: ${{ github.workspace }}/frontend/apps/remark42/node_modules/.bin/codecov
|
||||||
env:
|
env:
|
||||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ name: site
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
|
- master
|
||||||
tags:
|
tags:
|
||||||
paths:
|
paths:
|
||||||
- ".github/workflows/ci-site.yml"
|
- ".github/workflows/ci-site.yml"
|
||||||
@@ -13,20 +14,20 @@ on:
|
|||||||
- "site/**"
|
- "site/**"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
build:
|
||||||
build-site:
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: set up QEMU
|
- name: set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|
||||||
- name: set up Docker Buildx
|
- name: set up Docker Buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
- name: available platforms
|
- name: available platforms
|
||||||
run: echo ${{ steps.buildx.outputs.platforms }}
|
run: echo ${{ steps.buildx.outputs.platforms }}
|
||||||
@@ -44,7 +45,7 @@ jobs:
|
|||||||
echo GITHUB_REF - $ref
|
echo GITHUB_REF - $ref
|
||||||
echo ${GITHUB_PACKAGE_TOKEN} | docker login ghcr.io -u ${USERNAME} --password-stdin
|
echo ${GITHUB_PACKAGE_TOKEN} | docker login ghcr.io -u ${USERNAME} --password-stdin
|
||||||
docker buildx build --push --no-cache --platform linux/amd64,linux/arm/v7,linux/arm64 \
|
docker buildx build --push --no-cache --platform linux/amd64,linux/arm/v7,linux/arm64 \
|
||||||
-t ghcr.io/${USERNAME}/remark24-site:${ref} .
|
-t ghcr.io/umputun/remark42-site:${ref} .
|
||||||
|
|
||||||
- name: deploy tagged (latest) to ghcr.io and dockerhub
|
- name: deploy tagged (latest) to ghcr.io and dockerhub
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
@@ -59,7 +60,7 @@ jobs:
|
|||||||
echo "GITHUB_REF=$ref, GITHUB_SHA=${GITHUB_SHA}"
|
echo "GITHUB_REF=$ref, GITHUB_SHA=${GITHUB_SHA}"
|
||||||
echo ${GITHUB_PACKAGE_TOKEN} | docker login ghcr.io -u ${USERNAME} --password-stdin
|
echo ${GITHUB_PACKAGE_TOKEN} | docker login ghcr.io -u ${USERNAME} --password-stdin
|
||||||
docker buildx build --push --no-cache --platform linux/amd64,linux/arm/v7,linux/arm64 \
|
docker buildx build --push --no-cache --platform linux/amd64,linux/arm/v7,linux/arm64 \
|
||||||
-t ghcr.io/${USERNAME}/remark24-site:${ref} -t ghcr.io/${USERNAME}/remark24-site:latest .
|
-t ghcr.io/umputun/remark42-site:${ref} -t ghcr.io/umputun/remark42-site:latest .
|
||||||
|
|
||||||
- name: remote site deployment from master
|
- name: remote site deployment from master
|
||||||
if: ${{ github.ref == 'refs/heads/master' }}
|
if: ${{ github.ref == 'refs/heads/master' }}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
name: e2e
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
paths:
|
||||||
|
- "frontend/apps/remark42/**"
|
||||||
|
- "frontend/e2e/**"
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches: [master]
|
||||||
|
paths:
|
||||||
|
- "frontend/apps/remark42/**"
|
||||||
|
- "frontend/e2e/**"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
name: Tests
|
||||||
|
timeout-minutes: 60
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Build & run containers
|
||||||
|
id: tests
|
||||||
|
run: COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose -f compose-e2e-test.yml up --build --quiet-pull --exit-code-from tests
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: playwright-report
|
||||||
|
path: ./playwright-report/
|
||||||
|
retention-days: 30
|
||||||
+2
-2
@@ -4,7 +4,6 @@ target
|
|||||||
/logs/
|
/logs/
|
||||||
/target/
|
/target/
|
||||||
/var/
|
/var/
|
||||||
/web/
|
|
||||||
debug
|
debug
|
||||||
debug.test
|
debug.test
|
||||||
.vscode
|
.vscode
|
||||||
@@ -18,10 +17,11 @@ remark42
|
|||||||
/bin/
|
/bin/
|
||||||
/backend/var/
|
/backend/var/
|
||||||
/backend/app/var/
|
/backend/app/var/
|
||||||
/backend/web/
|
/backend/app/cmd/web/
|
||||||
/backend/*.html.tmpl
|
/backend/*.html.tmpl
|
||||||
compose-private-backend.yml
|
compose-private-backend.yml
|
||||||
compose-private-frontend.yml
|
compose-private-frontend.yml
|
||||||
compose-private.yml
|
compose-private.yml
|
||||||
/backend/_example/*/vendor
|
/backend/_example/*/vendor
|
||||||
http-client.env.json
|
http-client.env.json
|
||||||
|
/playwright-report/
|
||||||
|
|||||||
+65
-50
@@ -1,4 +1,51 @@
|
|||||||
FROM umputun/baseimage:buildgo-v1.8.0 as build-backend
|
FROM --platform=$BUILDPLATFORM node:16.15.1-alpine AS frontend-deps
|
||||||
|
|
||||||
|
ARG SKIP_FRONTEND_TEST
|
||||||
|
ARG SKIP_FRONTEND_BUILD
|
||||||
|
|
||||||
|
WORKDIR /srv/frontend/
|
||||||
|
|
||||||
|
COPY ./frontend/package.json ./frontend/pnpm-lock.yaml ./frontend/pnpm-workspace.yaml /srv/frontend/
|
||||||
|
COPY ./frontend/apps/remark42/package.json /srv/frontend/apps/remark42/
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
if [[ -z "$SKIP_FRONTEND_BUILD" || -z "$SKIP_FRONTEND_TEST" ]]; then \
|
||||||
|
apk add --no-cache --update git && \
|
||||||
|
npm i -g pnpm; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
RUN --mount=type=cache,id=pnpm,target=/root/.pnpm-store/v3 \
|
||||||
|
if [[ -z "$SKIP_FRONTEND_BUILD" || -z "$SKIP_FRONTEND_TEST" ]]; then \
|
||||||
|
pnpm i; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
FROM --platform=$BUILDPLATFORM frontend-deps AS build-frontend
|
||||||
|
|
||||||
|
ARG SKIP_FRONTEND_TEST
|
||||||
|
ARG SKIP_FRONTEND_BUILD
|
||||||
|
ENV CI=true
|
||||||
|
|
||||||
|
WORKDIR /srv/frontend/apps/remark42/
|
||||||
|
|
||||||
|
COPY ./frontend/apps/remark42/ /srv/frontend/apps/remark42/
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
if [ -z "$SKIP_FRONTEND_TEST" ]; then \
|
||||||
|
pnpm lint type-check translation-check test; \
|
||||||
|
else \
|
||||||
|
echo 'Skip frontend test'; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
if [ -z "$SKIP_FRONTEND_BUILD" ]; then \
|
||||||
|
pnpm build; \
|
||||||
|
else \
|
||||||
|
mkdir /srv/frontend/apps/remark42/public; \
|
||||||
|
echo 'Skip frontend build'; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
FROM umputun/baseimage:buildgo-v1.9.2 as build-backend
|
||||||
|
|
||||||
ARG CI
|
ARG CI
|
||||||
ARG GITHUB_REF
|
ARG GITHUB_REF
|
||||||
@@ -8,11 +55,11 @@ ARG SKIP_BACKEND_TEST
|
|||||||
ARG BACKEND_TEST_TIMEOUT
|
ARG BACKEND_TEST_TIMEOUT
|
||||||
|
|
||||||
ADD backend /build/backend
|
ADD backend /build/backend
|
||||||
ADD .git/ /build/backend/.git/
|
# to embed the frontend files statically into Remark42 binary
|
||||||
|
COPY --from=build-frontend /srv/frontend/apps/remark42/public/ /build/backend/app/cmd/web/
|
||||||
|
RUN find /build/backend/app/cmd/web/ -regex '.*\.\(html\|js\|mjs\)$' -print -exec sed -i "s|{% REMARK_URL %}|http://127.0.0.1:8080|g" {} \;
|
||||||
WORKDIR /build/backend
|
WORKDIR /build/backend
|
||||||
|
|
||||||
ENV GOFLAGS="-mod=vendor"
|
|
||||||
|
|
||||||
# install gcc in order to be able to go test package with -race
|
# install gcc in order to be able to go test package with -race
|
||||||
RUN apk --no-cache add gcc libc-dev
|
RUN apk --no-cache add gcc libc-dev
|
||||||
|
|
||||||
@@ -26,7 +73,7 @@ RUN \
|
|||||||
cat /profile.cov_tmp | grep -v "_mock.go" > /profile.cov ; \
|
cat /profile.cov_tmp | grep -v "_mock.go" > /profile.cov ; \
|
||||||
golangci-lint run --config ../.golangci.yml ./... ; \
|
golangci-lint run --config ../.golangci.yml ./... ; \
|
||||||
else \
|
else \
|
||||||
echo "skip backend tests and linter" \
|
echo "skip backend tests and linter" \
|
||||||
; fi
|
; fi
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
@@ -34,65 +81,33 @@ RUN \
|
|||||||
echo "version=$version" && \
|
echo "version=$version" && \
|
||||||
go build -o remark42 -ldflags "-X main.revision=${version} -s -w" ./app
|
go build -o remark42 -ldflags "-X main.revision=${version} -s -w" ./app
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM node:16.13.2-alpine as build-frontend-deps
|
FROM umputun/baseimage:app-v1.9.2
|
||||||
|
|
||||||
ARG CI
|
ARG GITHUB_SHA
|
||||||
ARG SKIP_FRONTEND_BUILD
|
|
||||||
ENV HUSKY_SKIP_INSTALL=true
|
|
||||||
|
|
||||||
RUN if [ -z "$SKIP_FRONTEND_BUILD" ] ; then \
|
LABEL org.opencontainers.image.authors="Umputun <umputun@gmail.com>" \
|
||||||
apk add --no-cache --update git \
|
org.opencontainers.image.description="Remark42 comment engine" \
|
||||||
; fi
|
org.opencontainers.image.documentation="https://remark42.com/docs/getting-started/" \
|
||||||
ADD frontend/package.json /srv/frontend/package.json
|
org.opencontainers.image.licenses="MIT" \
|
||||||
ADD frontend/package-lock.json /srv/frontend/package-lock.json
|
org.opencontainers.image.source="https://github.com/umputun/remark42.git" \
|
||||||
WORKDIR /srv/frontend
|
org.opencontainers.image.title="Remark42" \
|
||||||
RUN mkdir node_modules
|
org.opencontainers.image.url="https://remark42.com/" \
|
||||||
RUN if [ -z "$SKIP_FRONTEND_BUILD" ] ; then \
|
org.opencontainers.image.revision="${GITHUB_SHA}"
|
||||||
CI=true npm ci --loglevel warn \
|
|
||||||
else \
|
|
||||||
echo "skip frontend build" \
|
|
||||||
; fi
|
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM node:16.13.2-alpine as build-frontend
|
|
||||||
|
|
||||||
ARG CI
|
|
||||||
ARG SKIP_FRONTEND_TEST
|
|
||||||
ARG SKIP_FRONTEND_BUILD
|
|
||||||
ARG NODE_ENV=production
|
|
||||||
|
|
||||||
COPY --from=build-frontend-deps /srv/frontend/node_modules /srv/frontend/node_modules
|
|
||||||
ADD frontend /srv/frontend
|
|
||||||
WORKDIR /srv/frontend
|
|
||||||
RUN mkdir public
|
|
||||||
RUN if [ -z "$SKIP_FRONTEND_BUILD" ] ; then \
|
|
||||||
if [ -z "$SKIP_FRONTEND_TEST" ] ; then \
|
|
||||||
npm run lint test check; \
|
|
||||||
else \
|
|
||||||
echo "skip frontend tests and lint" ; npm run build \
|
|
||||||
; fi \
|
|
||||||
; fi
|
|
||||||
RUN rm -rf ./node_modules
|
|
||||||
|
|
||||||
FROM umputun/baseimage:app-v1.8.0
|
|
||||||
|
|
||||||
WORKDIR /srv
|
WORKDIR /srv
|
||||||
|
|
||||||
ADD docker-init.sh /entrypoint.sh
|
COPY docker-init.sh /srv/init.sh
|
||||||
ADD backend/scripts/backup.sh /usr/local/bin/backup
|
ADD backend/scripts/backup.sh /usr/local/bin/backup
|
||||||
ADD backend/scripts/restore.sh /usr/local/bin/restore
|
ADD backend/scripts/restore.sh /usr/local/bin/restore
|
||||||
ADD backend/scripts/import.sh /usr/local/bin/import
|
ADD backend/scripts/import.sh /usr/local/bin/import
|
||||||
RUN chmod +x /entrypoint.sh /usr/local/bin/backup /usr/local/bin/restore /usr/local/bin/import
|
RUN chmod +x /srv/init.sh /usr/local/bin/backup /usr/local/bin/restore /usr/local/bin/import
|
||||||
|
|
||||||
COPY --from=build-backend /build/backend/remark42 /srv/remark42
|
COPY --from=build-backend /build/backend/remark42 /srv/remark42
|
||||||
COPY --from=build-backend /build/backend/templates /srv
|
COPY --from=build-frontend /srv/frontend/apps/remark42/public/ /srv/web/
|
||||||
COPY --from=build-frontend /srv/frontend/public/ /srv/web
|
|
||||||
COPY docker-init.sh /srv/init.sh
|
|
||||||
RUN chown -R app:app /srv
|
RUN chown -R app:app /srv
|
||||||
RUN ln -s /srv/remark42 /usr/bin/remark42
|
RUN ln -s /srv/remark42 /usr/bin/remark42
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
HEALTHCHECK --interval=30s --timeout=3s CMD curl --fail http://localhost:8080/ping || exit 1
|
HEALTHCHECK --interval=30s --timeout=3s CMD curl --fail http://localhost:8080/ping || exit 1
|
||||||
|
|
||||||
|
|
||||||
RUN chmod +x /srv/init.sh
|
|
||||||
CMD ["/srv/remark42", "server"]
|
CMD ["/srv/remark42", "server"]
|
||||||
|
|||||||
+20
-32
@@ -1,53 +1,41 @@
|
|||||||
FROM node:12.16-alpine as build-frontend-deps
|
FROM node:16.15.1-alpine AS frontend-deps
|
||||||
|
|
||||||
ARG CI
|
ENV CI=true
|
||||||
|
|
||||||
ENV SKIP_FRONTEND_TEST=true
|
WORKDIR /srv/frontend
|
||||||
|
|
||||||
RUN apk add --no-cache --update git
|
COPY ./frontend/package.json ./frontend/pnpm-lock.yaml ./frontend/pnpm-workspace.yaml /srv/frontend/
|
||||||
ADD frontend/package.json /srv/frontend/package.json
|
COPY ./frontend/apps/remark42/package.json /srv/frontend/apps/remark42/package.json
|
||||||
ADD frontend/package-lock.json /srv/frontend/package-lock.json
|
|
||||||
RUN cd /srv/frontend && CI=true npm ci
|
|
||||||
|
|
||||||
FROM node:12.16-alpine as build-frontend
|
RUN apk add --no-cache --update git && npm i -g pnpm
|
||||||
|
RUN --mount=type=cache,id=pnpm,target=/root/.pnpm-store/v3 pnpm i
|
||||||
|
|
||||||
ARG CI
|
FROM frontend-deps AS build-frontend
|
||||||
ARG NODE_ENV=production
|
|
||||||
ENV SKIP_FRONTEND_TEST=true
|
|
||||||
ENV HUSKY_SKIP_INSTALL=true
|
|
||||||
|
|
||||||
COPY --from=build-frontend-deps /srv/frontend/node_modules /srv/frontend/node_modules
|
ENV NODE_ENV=production
|
||||||
ADD frontend /srv/frontend
|
ENV CI=true
|
||||||
RUN cd /srv/frontend && \
|
|
||||||
npm run build && \
|
|
||||||
rm -rf ./node_modules
|
|
||||||
|
|
||||||
FROM umputun/baseimage:buildgo-latest as build-backend
|
WORKDIR /srv/frontend/apps/remark42/
|
||||||
|
COPY ./frontend/apps/remark42/ /srv/frontend/apps/remark42/
|
||||||
|
RUN pnpm build
|
||||||
|
|
||||||
|
FROM umputun/baseimage:buildgo-v1.9.2 as build-backend
|
||||||
|
|
||||||
ARG GITHUB_TOKEN
|
ARG GITHUB_TOKEN
|
||||||
ENV SKIP_BACKEND_TEST=true
|
ARG GITHUB_REF
|
||||||
|
ARG GITHUB_SHA
|
||||||
|
|
||||||
WORKDIR /build/backend
|
WORKDIR /build/backend
|
||||||
ADD backend /build/backend
|
ADD backend /build/backend
|
||||||
ADD README.md /build/
|
ADD README.md /build/
|
||||||
ADD LICENSE /build/
|
ADD LICENSE /build/
|
||||||
|
|
||||||
ADD .git/ /build/backend/.git/
|
COPY --from=build-frontend /srv/frontend/apps/remark42/public/ /build/backend/app/cmd/web/
|
||||||
|
|
||||||
COPY --from=build-frontend /srv/frontend/public/ web
|
RUN find /build/backend/app/cmd/web/ -regex '.*\.\(html\|js\|mjs\)$' -print -exec sed -i "s|{% REMARK_URL %}|http://127.0.0.1:8080|g" {} \;
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
export WEB_ROOT=/build/backend/web && \
|
version=$("/script/version.sh") && echo "version=${version}" && \
|
||||||
find . -regex '.*\.\(html\|js\|mjs\)$' -print -exec sed -i "s|{% REMARK_URL %}|http://127.0.0.1:8080|g" {} \; && \
|
|
||||||
statik --src=${WEB_ROOT} --dest=/build/backend/app/rest -p api -f && \
|
|
||||||
statik --src=/build/backend/templates --dest=/build/backend/app -p templates -ns templates -f && \
|
|
||||||
ls -la /build/backend/app/templates/statik.go && \
|
|
||||||
ls -la /build/backend/app/rest/api/statik.go && \
|
|
||||||
ls -la /build/backend/web/
|
|
||||||
|
|
||||||
RUN \
|
|
||||||
version=$("/script/version.sh") && echo "version=${version}" && \
|
|
||||||
export GOFLAGS="-mod=vendor" && \
|
|
||||||
GOOS=linux GOARCH=amd64 go build -o remark42.linux-amd64 -ldflags "-X main.revision=${version} -s -w" ./app && \
|
GOOS=linux GOARCH=amd64 go build -o remark42.linux-amd64 -ldflags "-X main.revision=${version} -s -w" ./app && \
|
||||||
GOOS=linux GOARCH=386 go build -o remark42.linux-386 -ldflags "-X main.revision=${version} -s -w" ./app && \
|
GOOS=linux GOARCH=386 go build -o remark42.linux-386 -ldflags "-X main.revision=${version} -s -w" ./app && \
|
||||||
GOOS=linux GOARCH=arm go build -o remark42.linux-arm -ldflags "-X main.revision=${version} -s -w" ./app && \
|
GOOS=linux GOARCH=arm go build -o remark42.linux-arm -ldflags "-X main.revision=${version} -s -w" ./app && \
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
OS=linux
|
OS=linux
|
||||||
ARCH=amd64
|
ARCH=amd64
|
||||||
|
GITHUB_REF=$(shell git rev-parse --symbolic-full-name HEAD)
|
||||||
|
GITHUB_SHA=$(shell git rev-parse --short HEAD)
|
||||||
|
|
||||||
bin:
|
bin:
|
||||||
docker build -f Dockerfile.artifacts -t remark42.bin .
|
docker build -f Dockerfile.artifacts -t remark42.bin .
|
||||||
@@ -9,15 +11,18 @@ bin:
|
|||||||
docker rm -f remark42.bin
|
docker rm -f remark42.bin
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
docker build -t umputun/remark42 --build-arg SKIP_FRONTEND_TEST=true --build-arg SKIP_BACKEND_TEST=true .
|
DOCKER_BUILDKIT=1 docker build -t umputun/remark42 --build-arg GITHUB_REF=$(GITHUB_REF) --build-arg GITHUB_SHA=$(GITHUB_SHA) \
|
||||||
|
--build-arg CI=true --build-arg SKIP_FRONTEND_TEST=true --build-arg SKIP_BACKEND_TEST=true .
|
||||||
|
|
||||||
dockerx:
|
dockerx:
|
||||||
docker buildx build --build-arg SKIP_FRONTEND_TEST=true --build-arg SKIP_BACKEND_TEST=true \
|
docker buildx build --build-arg GITHUB_REF=$(GITHUB_REF) --build-arg GITHUB_SHA=$(GITHUB_SHA) --build-arg CI=true \
|
||||||
--progress=plain --platform linux/amd64,linux/arm/v7,linux/arm64 \
|
--build-arg SKIP_FRONTEND_TEST=true --build-arg SKIP_BACKEND_TEST=true \
|
||||||
-t ghcr.io/umputun/remark42:master -t umputun/remark42:master .
|
--progress=plain --platform linux/amd64,linux/arm/v7,linux/arm64 \
|
||||||
|
-t ghcr.io/umputun/remark42:master -t umputun/remark42:master .
|
||||||
|
|
||||||
release:
|
release:
|
||||||
docker build -f Dockerfile.artifacts --no-cache --pull -t remark42.bin .
|
docker build -f Dockerfile.artifacts --no-cache --pull --build-arg CI=true \
|
||||||
|
--build-arg GITHUB_REF=$(GITHUB_REF) --build-arg GITHUB_SHA=$(GITHUB_SHA) -t remark42.bin .
|
||||||
- @docker rm -f remark42.bin 2>/dev/null || exit 0
|
- @docker rm -f remark42.bin 2>/dev/null || exit 0
|
||||||
- @mkdir -p bin
|
- @mkdir -p bin
|
||||||
docker run -d --name=remark42.bin remark42.bin
|
docker run -d --name=remark42.bin remark42.bin
|
||||||
@@ -25,12 +30,13 @@ release:
|
|||||||
docker cp remark42.bin:/artifacts/remark42.linux-386.tar.gz bin/remark42.linux-386.tar.gz
|
docker cp remark42.bin:/artifacts/remark42.linux-386.tar.gz bin/remark42.linux-386.tar.gz
|
||||||
docker cp remark42.bin:/artifacts/remark42.linux-arm64.tar.gz bin/remark42.linux-arm64.tar.gz
|
docker cp remark42.bin:/artifacts/remark42.linux-arm64.tar.gz bin/remark42.linux-arm64.tar.gz
|
||||||
docker cp remark42.bin:/artifacts/remark42.darwin-amd64.tar.gz bin/remark42.darwin-amd64.tar.gz
|
docker cp remark42.bin:/artifacts/remark42.darwin-amd64.tar.gz bin/remark42.darwin-amd64.tar.gz
|
||||||
|
docker cp remark42.bin:/artifacts/remark42.darwin-arm64.tar.gz bin/remark42.darwin-arm64.tar.gz
|
||||||
docker cp remark42.bin:/artifacts/remark42.freebsd-amd64.tar.gz bin/remark42.freebsd-amd64.tar.gz
|
docker cp remark42.bin:/artifacts/remark42.freebsd-amd64.tar.gz bin/remark42.freebsd-amd64.tar.gz
|
||||||
docker cp remark42.bin:/artifacts/remark42.windows-amd64.zip bin/remark42.windows-amd64.zip
|
docker cp remark42.bin:/artifacts/remark42.windows-amd64.zip bin/remark42.windows-amd64.zip
|
||||||
docker rm -f remark42.bin
|
docker rm -f remark42.bin
|
||||||
|
|
||||||
race_test:
|
race_test:
|
||||||
cd backend/app && go test -race -mod=vendor -timeout=60s -count 1 ./...
|
cd backend/app && go test -race -timeout=60s -count 1 ./...
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
docker-compose -f compose-dev-backend.yml build
|
docker-compose -f compose-dev-backend.yml build
|
||||||
@@ -39,8 +45,8 @@ frontend:
|
|||||||
docker-compose -f compose-dev-frontend.yml build
|
docker-compose -f compose-dev-frontend.yml build
|
||||||
|
|
||||||
rundev:
|
rundev:
|
||||||
docker pull umputun/baseimage:buildgo-latest
|
SKIP_BACKEND_TEST=true SKIP_FRONTEND_TEST=true GITHUB_REF=$(GITHUB_REF) GITHUB_SHA=$(GITHUB_SHA) CI=true \
|
||||||
SKIP_BACKEND_TEST=true SKIP_FRONTEND_TEST=true docker-compose -f compose-private.yml build
|
docker-compose -f compose-private.yml build
|
||||||
docker-compose -f compose-private.yml up
|
docker-compose -f compose-private.yml up
|
||||||
|
|
||||||
.PHONY: bin backend
|
.PHONY: bin backend
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Remark42 [](https://github.com/umputun/remark42/actions) [](https://goreportcard.com/report/github.com/umputun/remark42) [](https://coveralls.io/github/umputun/remark42?branch=master) [](https://app.codecov.io/gh/umputun/remark42)
|
# Remark42 [](https://github.com/umputun/remark42/actions) [](https://hub.docker.com/r/umputun/remark42) [](https://goreportcard.com/report/github.com/umputun/remark42) [](https://coveralls.io/github/umputun/remark42?branch=master) [](https://app.codecov.io/gh/umputun/remark42)
|
||||||
|
|
||||||
Remark42 is a self-hosted, lightweight and simple (yet functional) comment engine, which doesn't spy on users. It can be embedded into blogs, articles, or any other place where readers add comments.
|
Remark42 is a self-hosted, lightweight and simple (yet functional) comment engine, which doesn't spy on users. It can be embedded into blogs, articles, or any other place where readers add comments.
|
||||||
|
|
||||||
@@ -37,6 +37,12 @@ For admin screenshots see [Admin UI documentation](https://remark42.com/docs/man
|
|||||||
|
|
||||||
All remark42 documentation is available [by the link](https://remark42.com/docs/getting-started/installation/).
|
All remark42 documentation is available [by the link](https://remark42.com/docs/getting-started/installation/).
|
||||||
|
|
||||||
|
## Contribution
|
||||||
|
|
||||||
|
In order to start and work on the project locally in development mode check our contribution documentation for [backend](https://remark42.com/docs/contributing/backend/) and [frontend](https://remark42.com/docs/contributing/frontend/).
|
||||||
|
|
||||||
|
If you are interested in adding a new localization please check [these docs](https://remark42.com/docs/contributing/translations/).
|
||||||
|
|
||||||
## Related projects
|
## Related projects
|
||||||
|
|
||||||
* [A Helm chart for Remark42 on Kubernetes](https://github.com/groundhog2k/helm-charts/tree/master/charts/remark42)
|
* [A Helm chart for Remark42 on Kubernetes](https://github.com/groundhog2k/helm-charts/tree/master/charts/remark42)
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ run:
|
|||||||
linters-settings:
|
linters-settings:
|
||||||
govet:
|
govet:
|
||||||
check-shadowing: true
|
check-shadowing: true
|
||||||
golint:
|
|
||||||
min-confidence: 0.1
|
|
||||||
maligned:
|
maligned:
|
||||||
suggest-new: true
|
suggest-new: true
|
||||||
goconst:
|
goconst:
|
||||||
@@ -37,18 +35,14 @@ linters:
|
|||||||
- revive
|
- revive
|
||||||
- govet
|
- govet
|
||||||
- unconvert
|
- unconvert
|
||||||
- megacheck
|
|
||||||
- structcheck
|
|
||||||
- gas
|
- gas
|
||||||
- gocyclo
|
- gocyclo
|
||||||
- dupl
|
- dupl
|
||||||
- misspell
|
- misspell
|
||||||
- unparam
|
- unparam
|
||||||
- varcheck
|
- unused
|
||||||
- deadcode
|
|
||||||
- typecheck
|
- typecheck
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- varcheck
|
|
||||||
- stylecheck
|
- stylecheck
|
||||||
- gochecknoinits
|
- gochecknoinits
|
||||||
- exportloopref
|
- exportloopref
|
||||||
@@ -64,9 +58,9 @@ issues:
|
|||||||
- text: "at least one file in a package should have a package comment"
|
- text: "at least one file in a package should have a package comment"
|
||||||
linters:
|
linters:
|
||||||
- stylecheck
|
- stylecheck
|
||||||
- text: "should have a package comment, unless it's in another file for this package"
|
- text: "package-comments: should have a package comment"
|
||||||
linters:
|
linters:
|
||||||
- golint
|
- revive
|
||||||
- path: _test\.go
|
- path: _test\.go
|
||||||
linters:
|
linters:
|
||||||
- gosec
|
- gosec
|
||||||
|
|||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
../site/src/docs/contributing/backend/index.md
|
||||||
@@ -1,12 +1,22 @@
|
|||||||
FROM umputun/baseimage:buildgo-latest as build-backend
|
FROM umputun/baseimage:buildgo-v1.9.2 as build-backend
|
||||||
|
|
||||||
ADD backend /build/backend
|
ADD backend /build/backend
|
||||||
WORKDIR /build/backend/_example/memory_store
|
WORKDIR /build/backend/_example/memory_store
|
||||||
|
|
||||||
RUN go build -o /build/bin/memory_store -ldflags "-X main.revision=0.0.0 -s -w"
|
RUN go build -o /build/bin/memory_store -ldflags "-X main.revision=0.0.0 -s -w"
|
||||||
|
|
||||||
|
FROM umputun/baseimage:app-v1.9.2
|
||||||
|
|
||||||
FROM umputun/baseimage:app-latest
|
ARG GITHUB_SHA
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.authors="Umputun <umputun@gmail.com>" \
|
||||||
|
org.opencontainers.image.description="Remark42 comment engine example JRPC memory store" \
|
||||||
|
org.opencontainers.image.documentation="https://github.com/umputun/remark42/tree/master/backend/_example/memory_store" \
|
||||||
|
org.opencontainers.image.licenses="MIT" \
|
||||||
|
org.opencontainers.image.source="https://github.com/umputun/remark42.git" \
|
||||||
|
org.opencontainers.image.title="Remark42 JRPC example memory store" \
|
||||||
|
org.opencontainers.image.url="https://remark42.com/" \
|
||||||
|
org.opencontainers.image.revision="${GITHUB_SHA}"
|
||||||
|
|
||||||
WORKDIR /srv
|
WORKDIR /srv
|
||||||
COPY --from=build-backend /build/bin/memory_store /srv/memory_store
|
COPY --from=build-backend /build/bin/memory_store /srv/memory_store
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ package accessor
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/go-pkgz/lgr"
|
||||||
"github.com/umputun/remark42/backend/app/store"
|
"github.com/umputun/remark42/backend/app/store"
|
||||||
"github.com/umputun/remark42/backend/app/store/engine"
|
"github.com/umputun/remark42/backend/app/store/engine"
|
||||||
)
|
)
|
||||||
@@ -267,7 +267,7 @@ func (m *MemData) ListFlags(req engine.FlagRequest) (res []interface{}, err erro
|
|||||||
return res, nil
|
return res, nil
|
||||||
|
|
||||||
case engine.Blocked:
|
case engine.Blocked:
|
||||||
log.Printf("%+v", m.metaUsers)
|
log.Printf("[INFO] metaUsers: %+v", m.metaUsers)
|
||||||
for _, u := range m.metaUsers {
|
for _, u := range m.metaUsers {
|
||||||
if u.SiteID == req.Locator.SiteID && u.Blocked && u.BlockedUntil.After(time.Now()) {
|
if u.SiteID == req.Locator.SiteID && u.Blocked && u.BlockedUntil.After(time.Now()) {
|
||||||
res = append(res, store.BlockedUser{ID: u.UserID, Until: u.BlockedUntil})
|
res = append(res, store.BlockedUser{ID: u.UserID, Until: u.BlockedUntil})
|
||||||
|
|||||||
@@ -3,42 +3,40 @@ module github.com/umputun/remark42/memory_store
|
|||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-pkgz/jrpc v0.2.0
|
github.com/go-pkgz/jrpc v0.3.0
|
||||||
github.com/go-pkgz/lgr v0.10.4
|
github.com/go-pkgz/lgr v0.10.4
|
||||||
github.com/jessevdk/go-flags v1.5.0
|
github.com/jessevdk/go-flags v1.5.0
|
||||||
github.com/stretchr/testify v1.7.1
|
github.com/stretchr/testify v1.8.0
|
||||||
github.com/umputun/remark42/backend v1.9.0
|
github.com/umputun/remark42/backend v1.10.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Depado/bfchroma v1.3.0 // indirect
|
github.com/Depado/bfchroma/v2 v2.0.0 // indirect
|
||||||
github.com/PuerkitoBio/goquery v1.8.0 // indirect
|
github.com/PuerkitoBio/goquery v1.8.0 // indirect
|
||||||
github.com/alecthomas/chroma v0.10.0 // indirect
|
github.com/ajg/form v1.5.1 // indirect
|
||||||
|
github.com/alecthomas/chroma/v2 v2.3.0 // indirect
|
||||||
github.com/andybalholm/cascadia v1.3.1 // indirect
|
github.com/andybalholm/cascadia v1.3.1 // indirect
|
||||||
github.com/aymerick/douceur v0.2.0 // indirect
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/didip/tollbooth/v6 v6.1.2 // indirect
|
github.com/didip/tollbooth/v7 v7.0.1 // indirect
|
||||||
github.com/didip/tollbooth_chi v0.0.0-20220429013743-da966f2f674b // indirect
|
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f // indirect
|
||||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||||
github.com/go-chi/chi v4.1.1+incompatible // indirect
|
github.com/go-chi/chi/v5 v5.0.7 // indirect
|
||||||
github.com/go-chi/render v1.0.1 // indirect
|
github.com/go-chi/render v1.0.2 // indirect
|
||||||
github.com/go-pkgz/expirable-cache v0.0.3 // indirect
|
github.com/go-pkgz/expirable-cache v0.1.0 // indirect
|
||||||
github.com/go-pkgz/rest v1.15.0 // indirect
|
github.com/go-pkgz/rest v1.16.0 // indirect
|
||||||
github.com/gorilla/css v1.0.0 // indirect
|
github.com/gorilla/css v1.0.0 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/microcosm-cc/bluemonday v1.0.18 // indirect
|
github.com/microcosm-cc/bluemonday v1.0.20 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rs/xid v1.4.0 // indirect
|
github.com/rs/xid v1.4.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/stretchr/objx v0.3.0 // indirect
|
|
||||||
go.etcd.io/bbolt v1.3.6 // indirect
|
go.etcd.io/bbolt v1.3.6 // indirect
|
||||||
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 // indirect
|
golang.org/x/image v0.0.0-20220617043117-41969df76e82 // indirect
|
||||||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect
|
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect
|
||||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
|
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
|
||||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/umputun/remark42/backend => ../../
|
replace github.com/umputun/remark42/backend => ../../
|
||||||
|
|||||||
@@ -1,45 +1,40 @@
|
|||||||
github.com/Depado/bfchroma v1.3.0 h1:zz14vpvySU6S0CL6yGPr1vkFevQecIt8dJdCsMS2JpM=
|
github.com/Depado/bfchroma/v2 v2.0.0 h1:IRpN9BPkNwEpR6w1ectIcNWOuhDSLx+8f1pn83fzxx8=
|
||||||
github.com/Depado/bfchroma v1.3.0/go.mod h1:c0bFk0tFmT+clD3TIGurjWCfD/QV8/EebfM3JGr+98M=
|
github.com/Depado/bfchroma/v2 v2.0.0/go.mod h1:wFwW/Pw8Tnd0irzgO9Zxtxgzp3aPS8qBWlyadxujxmw=
|
||||||
github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
|
github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
|
||||||
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
|
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
|
||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||||
github.com/alecthomas/chroma v0.7.3/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||||
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
|
github.com/alecthomas/chroma/v2 v2.3.0 h1:83xfxrnjv8eK+Cf8qZDzNo3PPF9IbTWHs7z28GY6D0U=
|
||||||
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
|
github.com/alecthomas/chroma/v2 v2.3.0/go.mod h1:mZxeWZlxP2Dy+/8cBob2PYd8O2DwNAzave5AY7A2eQw=
|
||||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
|
||||||
github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
|
|
||||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
|
||||||
github.com/alecthomas/repr v0.0.0-20200325044227-4184120f674c/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
|
||||||
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
|
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
|
||||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/didip/tollbooth/v6 v6.0.1/go.mod h1:j2pKs+JQ5PvU/K4jFnrnwntrmfUbYLJE5oSdxR37FD0=
|
github.com/didip/tollbooth/v7 v7.0.0/go.mod h1:VZhDSGl5bDSPj4wPsih3PFa4Uh9Ghv8hgacaTm5PRT4=
|
||||||
github.com/didip/tollbooth/v6 v6.1.2 h1:Kdqxmqw9YTv0uKajBUiWQg+GURL/k4vy9gmLCL01PjQ=
|
github.com/didip/tollbooth/v7 v7.0.1 h1:TkT4sBKoQoHQFPf7blQ54iHrZiTDnr8TceU+MulVAog=
|
||||||
github.com/didip/tollbooth/v6 v6.1.2/go.mod h1:xjcse6CTHCLuOkzsWrEgdy9WPJFv+p/x6v+MyfP+O9s=
|
github.com/didip/tollbooth/v7 v7.0.1/go.mod h1:VZhDSGl5bDSPj4wPsih3PFa4Uh9Ghv8hgacaTm5PRT4=
|
||||||
github.com/didip/tollbooth_chi v0.0.0-20200524181329-8b84cd7183d9/go.mod h1:YWyIfq3y4ArRfWZ9XksmuusP+7Mad+T0iFZ0kv0XG/M=
|
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f h1:jtKwihcLmUC9BAhoJ9adCUqdSSZcOdH2KL7mPTUm2aw=
|
||||||
github.com/didip/tollbooth_chi v0.0.0-20220429013743-da966f2f674b h1:elkngQhLBcyDIXwL9Z7AfXXbBszUEsLxqNw6WPF8Mtc=
|
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f/go.mod h1:q9C80dnsuVRP2dAskjnXRNWdUJqtGgwG9wNrzt0019s=
|
||||||
github.com/didip/tollbooth_chi v0.0.0-20220429013743-da966f2f674b/go.mod h1:0ZVa6kSzS011nfTC1rELyxK4tjVf6vqBnOv7oY2KlsA=
|
|
||||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
|
||||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/go-chi/chi v4.1.1+incompatible h1:MmTgB0R8Bt/jccxp+t6S/1VGIKdJw5J74CK/c9tTfA4=
|
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
|
||||||
github.com/go-chi/chi v4.1.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
|
|
||||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||||
github.com/go-pkgz/expirable-cache v0.0.3 h1:rTh6qNPp78z0bQE6HDhXBHUwqnV9i09Vm6dksJLXQDc=
|
github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
|
||||||
github.com/go-pkgz/expirable-cache v0.0.3/go.mod h1:+IauqN00R2FqNRLCLA+X5YljQJrwB179PfiAoMPlTlQ=
|
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||||
github.com/go-pkgz/jrpc v0.2.0 h1:CLy/eZyekjraVrxZV18N2R1mYLMJ/nWrgdfyIOGPY/E=
|
github.com/go-pkgz/expirable-cache v0.1.0 h1:3bw0m8vlTK8qlwz5KXuygNBTkiKRTPrAGXU0Ej2AC1g=
|
||||||
github.com/go-pkgz/jrpc v0.2.0/go.mod h1:wd8vtQ4CgtCnuqua6x2b1SKIgv0VSOh5Dn0uUITbiUE=
|
github.com/go-pkgz/expirable-cache v0.1.0/go.mod h1:GTrEl0X+q0mPNqN6dtcQXksACnzCBQ5k/k1SwXJsZKs=
|
||||||
|
github.com/go-pkgz/jrpc v0.3.0 h1:Fls38KqPsHzvp0FWfivr6cGnncC+iFBodHBqvUPY+0U=
|
||||||
|
github.com/go-pkgz/jrpc v0.3.0/go.mod h1:MFtKs75JESiSqVicsQkgN2iDFFuCd3gVT1/vKiwRi00=
|
||||||
github.com/go-pkgz/lgr v0.10.4 h1:l7qyFjqEZgwRgaQQSEp6tve4A3OU80VrfzpvtEX8ngw=
|
github.com/go-pkgz/lgr v0.10.4 h1:l7qyFjqEZgwRgaQQSEp6tve4A3OU80VrfzpvtEX8ngw=
|
||||||
github.com/go-pkgz/lgr v0.10.4/go.mod h1:CD0s1z6EFpIUplV067gitF77tn25JItzwHNKAPqeCF0=
|
github.com/go-pkgz/lgr v0.10.4/go.mod h1:CD0s1z6EFpIUplV067gitF77tn25JItzwHNKAPqeCF0=
|
||||||
github.com/go-pkgz/rest v1.5.0/go.mod h1:nQaM3RhSTUAmbBZWY4hfe4buyeC9VckvhoCktiQXJxI=
|
github.com/go-pkgz/rest v1.15.6/go.mod h1:KUWAqbDteYGS/CiXftomQsKjtEOifXsJ36Ka0skYbmk=
|
||||||
github.com/go-pkgz/rest v1.15.0 h1:v/BDqJF9robo85GME85GWJ7O/NjtAO0x7LvO4EqWNRE=
|
github.com/go-pkgz/rest v1.16.0 h1:3An51XJXUq1XtICHvfAEp048ZCbUHBEga3g0WEu3zK0=
|
||||||
github.com/go-pkgz/rest v1.15.0/go.mod h1:KUWAqbDteYGS/CiXftomQsKjtEOifXsJ36Ka0skYbmk=
|
github.com/go-pkgz/rest v1.16.0/go.mod h1:HHlLOt02NJc2sgffXBF6hYVMcRo4Gz3vjg43zTzN7VM=
|
||||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
@@ -54,65 +49,42 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
|||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/microcosm-cc/bluemonday v1.0.20 h1:flpzsq4KU3QIYAYGV/szUat7H+GPOXR0B2JU5A1Wp8Y=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/microcosm-cc/bluemonday v1.0.20/go.mod h1:yfBmMi8mxvaZut3Yytv+jTXRY8mxyjJ0/kQBTElld50=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.18 h1:6HcxvXDAi3ARt3slx6nTesbvorIc3QeTzBNRvWktHBo=
|
|
||||||
github.com/microcosm-cc/bluemonday v1.0.18/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
|
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
|
github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
|
||||||
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/image v0.0.0-20220617043117-41969df76e82 h1:KpZB5pUSBvrHltNEdK/tw0xlPeD13M6M6aGP32gKqiw=
|
||||||
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 h1:LRtI4W37N+KFebI/qV0OFiLUv4GLOWeEW5hn/KEJvxE=
|
golang.org/x/image v0.0.0-20220617043117-41969df76e82/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||||
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
|
||||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y=
|
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY=
|
||||||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
|
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
|
||||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
|
|
||||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306/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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -43,16 +43,14 @@ func main() {
|
|||||||
adminStore := accessor.NewMemAdminStore(opts.Secret)
|
adminStore := accessor.NewMemAdminStore(opts.Secret)
|
||||||
imgStore := accessor.NewMemImageStore()
|
imgStore := accessor.NewMemImageStore()
|
||||||
|
|
||||||
rpcServer := jrpc.Server{
|
rpcServer := jrpc.NewServer(
|
||||||
API: opts.API,
|
opts.API,
|
||||||
AuthUser: opts.AuthUser,
|
jrpc.Auth(opts.AuthUser, opts.AuthPasswd),
|
||||||
AuthPasswd: opts.AuthPasswd,
|
jrpc.WithSignature("remark42-memory", "umputun", revision),
|
||||||
Version: revision,
|
jrpc.WithLogger(log.Default()),
|
||||||
AppName: "remark42-memory",
|
)
|
||||||
Logger: log.Default(),
|
|
||||||
}
|
|
||||||
|
|
||||||
srv := server.NewRPC(dataStore, adminStore, imgStore, &rpcServer)
|
srv := server.NewRPC(dataStore, adminStore, imgStore, rpcServer)
|
||||||
|
|
||||||
admRec := accessor.AdminRec{
|
admRec := accessor.AdminRec{
|
||||||
SiteID: "remark",
|
SiteID: "remark",
|
||||||
|
|||||||
@@ -198,25 +198,62 @@ func TestRPC_listFlagsHndl(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "123456", id)
|
assert.Equal(t, "123456", id)
|
||||||
|
|
||||||
flagReq := engine.FlagRequest{
|
// verify user
|
||||||
|
verifyFlagReq := engine.FlagRequest{
|
||||||
Flag: engine.Verified,
|
Flag: engine.Verified,
|
||||||
UserID: "u1",
|
UserID: "u1",
|
||||||
Locator: store.Locator{
|
Locator: store.Locator{
|
||||||
SiteID: "test-site",
|
SiteID: "test-site",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
flags, err := re.ListFlags(flagReq)
|
flags, err := re.ListFlags(verifyFlagReq)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, []interface{}{}, flags)
|
assert.Empty(t, flags)
|
||||||
|
|
||||||
flagReq.Update = engine.FlagTrue
|
verifyFlagReq.Update = engine.FlagTrue
|
||||||
status, err := re.Flag(flagReq)
|
status, err := re.Flag(verifyFlagReq)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, true, status)
|
assert.Equal(t, true, status)
|
||||||
|
|
||||||
flags, err = re.ListFlags(flagReq)
|
flags, err = re.ListFlags(verifyFlagReq)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, []interface{}{"u1"}, flags)
|
assert.Equal(t, []interface{}{"u1"}, flags)
|
||||||
|
verifiedUsers := make([]string, 0, len(flags))
|
||||||
|
for _, v := range flags {
|
||||||
|
verifiedUsers = append(verifiedUsers, v.(string))
|
||||||
|
}
|
||||||
|
assert.Equal(t, []string{"u1"}, verifiedUsers)
|
||||||
|
|
||||||
|
// block user
|
||||||
|
blockFlagReq := engine.FlagRequest{
|
||||||
|
Flag: engine.Blocked,
|
||||||
|
UserID: "u1",
|
||||||
|
Locator: store.Locator{
|
||||||
|
SiteID: "test-site",
|
||||||
|
},
|
||||||
|
TTL: time.Hour,
|
||||||
|
}
|
||||||
|
flags, err = re.ListFlags(blockFlagReq)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, flags)
|
||||||
|
|
||||||
|
blockFlagReq.Update = engine.FlagTrue
|
||||||
|
status, err = re.Flag(blockFlagReq)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, true, status)
|
||||||
|
|
||||||
|
flags, err = re.ListFlags(blockFlagReq)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, flags)
|
||||||
|
blockedUsers := make([]store.BlockedUser, 0, len(flags))
|
||||||
|
for _, v := range flags {
|
||||||
|
blockedUsers = append(blockedUsers, v.(store.BlockedUser))
|
||||||
|
}
|
||||||
|
require.Equal(t, 1, len(blockedUsers))
|
||||||
|
blockedUserInfo := blockedUsers[0]
|
||||||
|
assert.Equal(t, "u1", blockedUserInfo.ID)
|
||||||
|
assert.True(t, blockedUserInfo.Until.After(time.Now().Add(time.Minute*59)), "blocked duration is more than 59m away")
|
||||||
|
assert.True(t, blockedUserInfo.Until.Before(time.Now().Add(time.Minute*61)), "blocked duration is less than 61m away")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRPC_userDetailHndl(t *testing.T) {
|
func TestRPC_userDetailHndl(t *testing.T) {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ func prepTestStore(t *testing.T) (port int, teardown func()) {
|
|||||||
mg := accessor.NewMemData()
|
mg := accessor.NewMemData()
|
||||||
adm := accessor.NewMemAdminStore("secret")
|
adm := accessor.NewMemAdminStore("secret")
|
||||||
img := accessor.NewMemImageStore()
|
img := accessor.NewMemImageStore()
|
||||||
s := NewRPC(mg, adm, img, &jrpc.Server{API: "/test", Logger: jrpc.NoOpLogger})
|
s := NewRPC(mg, adm, img, jrpc.NewServer("/test"))
|
||||||
|
|
||||||
admRec := accessor.AdminRec{
|
admRec := accessor.AdminRec{
|
||||||
SiteID: "test-site",
|
SiteID: "test-site",
|
||||||
|
|||||||
@@ -14,11 +14,10 @@ import (
|
|||||||
// BackupCommand set of flags and command for export
|
// BackupCommand set of flags and command for export
|
||||||
// ExportPath used as a separate element to leverage BACKUP_PATH. If ExportFile has a path (i.e. with /) BACKUP_PATH ignored.
|
// ExportPath used as a separate element to leverage BACKUP_PATH. If ExportFile has a path (i.e. with /) BACKUP_PATH ignored.
|
||||||
type BackupCommand struct {
|
type BackupCommand struct {
|
||||||
ExportPath string `short:"p" long:"path" env:"BACKUP_PATH" default:"./var/backup" description:"export path"`
|
ExportPath string `short:"p" long:"path" env:"BACKUP_PATH" default:"./var/backup" description:"export path"`
|
||||||
ExportFile string `short:"f" long:"file" default:"userbackup-{{.SITE}}-{{.TS}}.gz" description:"file name"`
|
ExportFile string `short:"f" long:"file" default:"userbackup-{{.SITE}}-{{.TS}}.gz" description:"file name"`
|
||||||
Site string `short:"s" long:"site" env:"SITE" default:"remark" description:"site name"`
|
|
||||||
Timeout time.Duration `long:"timeout" default:"60m" description:"export (backup) timeout"`
|
SupportCmdOpts
|
||||||
AdminPasswd string `long:"admin-passwd" env:"ADMIN_PASSWD" required:"true" description:"admin basic auth password"`
|
|
||||||
CommonOpts
|
CommonOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ import (
|
|||||||
|
|
||||||
// CleanupCommand set of flags and command for cleanup
|
// CleanupCommand set of flags and command for cleanup
|
||||||
type CleanupCommand struct {
|
type CleanupCommand struct {
|
||||||
Site string `short:"s" long:"site" env:"SITE" default:"remark" description:"site name"`
|
Dry bool `long:"dry" description:"dry mode, will not remove comments"`
|
||||||
Dry bool `long:"dry" description:"dry mode, will not remove comments"`
|
From string `long:"from" description:"from yyyymmdd"`
|
||||||
From string `long:"from" description:"from yyyymmdd"`
|
To string `long:"to" description:"from yyyymmdd"`
|
||||||
To string `long:"to" description:"from yyyymmdd"`
|
BadWords []string `short:"w" long:"bword" description:"bad word(s)"`
|
||||||
BadWords []string `short:"w" long:"bword" description:"bad word(s)"`
|
BadUsers []string `short:"u" long:"buser" description:"bad user(s)"`
|
||||||
BadUsers []string `short:"u" long:"buser" description:"bad user(s)"`
|
SetTitle bool `long:"title" description:"title mode, will not remove comments, but reset titles to page's title'"`
|
||||||
AdminPasswd string `long:"admin-passwd" env:"ADMIN_PASSWD" required:"true" description:"admin basic auth password"`
|
|
||||||
SetTitle bool `long:"title" description:"title mode, will not remove comments, but reset titles to page's title'"`
|
SupportCmdOpts
|
||||||
CommonOpts
|
CommonOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,14 @@ type CommonOpts struct {
|
|||||||
Revision string
|
Revision string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SupportCmdOpts is set of commands shared among similar commands like backup/restore and such.
|
||||||
|
// Order of fields defines the help command output order.
|
||||||
|
type SupportCmdOpts struct {
|
||||||
|
Site string `short:"s" long:"site" env:"SITE" default:"remark" description:"site name"`
|
||||||
|
AdminPasswd string `long:"admin-passwd" env:"ADMIN_PASSWD" default:"" description:"admin basic auth password"`
|
||||||
|
Timeout time.Duration `long:"timeout" default:"60m" description:"timeout for the command run"`
|
||||||
|
}
|
||||||
|
|
||||||
// DeprecatedFlag contains information about deprecated option
|
// DeprecatedFlag contains information about deprecated option
|
||||||
type DeprecatedFlag struct {
|
type DeprecatedFlag struct {
|
||||||
Old string
|
Old string
|
||||||
|
|||||||
@@ -8,18 +8,16 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
log "github.com/go-pkgz/lgr"
|
log "github.com/go-pkgz/lgr"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ImportCommand set of flags and command for import
|
// ImportCommand set of flags and command for import
|
||||||
type ImportCommand struct {
|
type ImportCommand struct {
|
||||||
InputFile string `short:"f" long:"file" description:"input file name" required:"true"`
|
InputFile string `short:"f" long:"file" description:"input file name" required:"true"`
|
||||||
Provider string `short:"p" long:"provider" default:"disqus" choice:"disqus" choice:"wordpress" choice:"commento" description:"import format"` //nolint
|
Provider string `short:"p" long:"provider" default:"disqus" choice:"disqus" choice:"wordpress" choice:"commento" description:"import format"` //nolint
|
||||||
Site string `short:"s" long:"site" env:"SITE" default:"remark" description:"site name"`
|
|
||||||
Timeout time.Duration `long:"timeout" default:"60m" description:"import timeout"`
|
SupportCmdOpts
|
||||||
AdminPasswd string `long:"admin-passwd" env:"ADMIN_PASSWD" required:"true" description:"admin basic auth password"`
|
|
||||||
CommonOpts
|
CommonOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
log "github.com/go-pkgz/lgr"
|
log "github.com/go-pkgz/lgr"
|
||||||
)
|
)
|
||||||
@@ -14,10 +13,9 @@ import (
|
|||||||
// RemapCommand set of flags and command for change linkage between comments to
|
// RemapCommand set of flags and command for change linkage between comments to
|
||||||
// different urls based on given rules (input file)
|
// different urls based on given rules (input file)
|
||||||
type RemapCommand struct {
|
type RemapCommand struct {
|
||||||
Site string `short:"s" long:"site" env:"SITE" default:"remark" description:"site name"`
|
InputFile string `short:"f" long:"file" description:"input file name" required:"true"`
|
||||||
InputFile string `short:"f" long:"file" description:"input file name" required:"true"`
|
|
||||||
AdminPasswd string `long:"admin-passwd" env:"ADMIN_PASSWD" required:"true" description:"admin basic auth password"`
|
SupportCmdOpts
|
||||||
Timeout time.Duration `long:"timeout" default:"60m" description:"remap timeout"`
|
|
||||||
CommonOpts
|
CommonOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ type RestoreCommand struct {
|
|||||||
ImportPath string `short:"p" long:"path" env:"BACKUP_PATH" default:"./var/backup" description:"export path"`
|
ImportPath string `short:"p" long:"path" env:"BACKUP_PATH" default:"./var/backup" description:"export path"`
|
||||||
ImportFile string `short:"f" long:"file" default:"userbackup-{{.SITE}}-{{.YYYYMMDD}}.gz" description:"file name" required:"true"`
|
ImportFile string `short:"f" long:"file" default:"userbackup-{{.SITE}}-{{.YYYYMMDD}}.gz" description:"file name" required:"true"`
|
||||||
|
|
||||||
Site string `short:"s" long:"site" env:"SITE" default:"remark" description:"site name"`
|
SupportCmdOpts
|
||||||
Timeout time.Duration `long:"timeout" default:"60m" description:"import timeout"`
|
|
||||||
AdminPasswd string `long:"admin-passwd" env:"ADMIN_PASSWD" required:"true" description:"admin basic auth password"`
|
|
||||||
CommonOpts
|
CommonOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,12 +27,10 @@ func (rc *RestoreCommand) Execute(args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
importer := ImportCommand{
|
importer := ImportCommand{
|
||||||
InputFile: fname,
|
InputFile: fname,
|
||||||
Site: rc.Site,
|
Provider: "native",
|
||||||
Provider: "native",
|
SupportCmdOpts: rc.SupportCmdOpts,
|
||||||
Timeout: rc.Timeout,
|
CommonOpts: rc.CommonOpts,
|
||||||
AdminPasswd: rc.AdminPasswd,
|
|
||||||
CommonOpts: rc.CommonOpts,
|
|
||||||
}
|
}
|
||||||
return importer.Execute(args)
|
return importer.Execute(args)
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-34
@@ -2,6 +2,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -41,6 +42,9 @@ import (
|
|||||||
"github.com/umputun/remark42/backend/app/templates"
|
"github.com/umputun/remark42/backend/app/templates"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed web
|
||||||
|
var webFS embed.FS
|
||||||
|
|
||||||
// ServerCommand with command line flags and env
|
// ServerCommand with command line flags and env
|
||||||
type ServerCommand struct {
|
type ServerCommand struct {
|
||||||
Store StoreGroup `group:"store" namespace:"store" env-namespace:"STORE"`
|
Store StoreGroup `group:"store" namespace:"store" env-namespace:"STORE"`
|
||||||
@@ -79,7 +83,7 @@ type ServerCommand struct {
|
|||||||
EnableEmoji bool `long:"emoji" env:"EMOJI" description:"enable emoji"`
|
EnableEmoji bool `long:"emoji" env:"EMOJI" description:"enable emoji"`
|
||||||
SimpleView bool `long:"simple-view" env:"SIMPLE_VIEW" description:"minimal comment editor mode"`
|
SimpleView bool `long:"simple-view" env:"SIMPLE_VIEW" description:"minimal comment editor mode"`
|
||||||
ProxyCORS bool `long:"proxy-cors" env:"PROXY_CORS" description:"disable internal CORS and delegate it to proxy"`
|
ProxyCORS bool `long:"proxy-cors" env:"PROXY_CORS" description:"disable internal CORS and delegate it to proxy"`
|
||||||
AllowedHosts []string `long:"allowed-hosts" env:"ALLOWED_HOSTS" description:"limit hosts/sources allowed to embed comments"`
|
AllowedHosts []string `long:"allowed-hosts" env:"ALLOWED_HOSTS" description:"limit hosts/sources allowed to embed comments" env-delim:","`
|
||||||
SubscribersOnly bool `long:"subscribers-only" env:"SUBSCRIBERS_ONLY" description:"enable commenting only for Patreon subscribers"`
|
SubscribersOnly bool `long:"subscribers-only" env:"SUBSCRIBERS_ONLY" description:"enable commenting only for Patreon subscribers"`
|
||||||
DisableSignature bool `long:"disable-signature" env:"DISABLE_SIGNATURE" description:"disable server signature in headers"`
|
DisableSignature bool `long:"disable-signature" env:"DISABLE_SIGNATURE" description:"disable server signature in headers"`
|
||||||
|
|
||||||
@@ -204,13 +208,14 @@ type TelegramGroup struct {
|
|||||||
|
|
||||||
// SMTPGroup defines options for SMTP server connection, used in auth and notify modules
|
// SMTPGroup defines options for SMTP server connection, used in auth and notify modules
|
||||||
type SMTPGroup struct {
|
type SMTPGroup struct {
|
||||||
Host string `long:"host" env:"HOST" description:"SMTP host"`
|
Host string `long:"host" env:"HOST" description:"SMTP host"`
|
||||||
Port int `long:"port" env:"PORT" description:"SMTP port"`
|
Port int `long:"port" env:"PORT" description:"SMTP port"`
|
||||||
Username string `long:"username" env:"USERNAME" description:"SMTP user name"`
|
Username string `long:"username" env:"USERNAME" description:"SMTP user name"`
|
||||||
Password string `long:"password" env:"PASSWORD" description:"SMTP password"`
|
Password string `long:"password" env:"PASSWORD" description:"SMTP password"`
|
||||||
TLS bool `long:"tls" env:"TLS" description:"enable TLS"`
|
TLS bool `long:"tls" env:"TLS" description:"enable TLS"`
|
||||||
StartTLS bool `long:"starttls" env:"STARTTLS" description:"enable StartTLS"`
|
LoginAuth bool `long:"login_auth" env:"LOGIN_AUTH" description:"enable LOGIN auth instead of PLAIN"`
|
||||||
TimeOut time.Duration `long:"timeout" env:"TIMEOUT" default:"10s" description:"SMTP TCP connection timeout"`
|
StartTLS bool `long:"starttls" env:"STARTTLS" description:"enable StartTLS"`
|
||||||
|
TimeOut time.Duration `long:"timeout" env:"TIMEOUT" default:"10s" description:"SMTP TCP connection timeout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotifyGroup defines options for notification
|
// NotifyGroup defines options for notification
|
||||||
@@ -556,6 +561,7 @@ func (s *ServerCommand) newServerApp(ctx context.Context) (*serverApp, error) {
|
|||||||
Version: s.Revision,
|
Version: s.Revision,
|
||||||
DataService: dataService,
|
DataService: dataService,
|
||||||
WebRoot: s.WebRoot,
|
WebRoot: s.WebRoot,
|
||||||
|
WebFS: webFS,
|
||||||
RemarkURL: s.RemarkURL,
|
RemarkURL: s.RemarkURL,
|
||||||
ImageProxy: imgProxy,
|
ImageProxy: imgProxy,
|
||||||
CommentFormatter: commentFormatter,
|
CommentFormatter: commentFormatter,
|
||||||
@@ -860,7 +866,11 @@ func (s *ServerCommand) addAuthProviders(authenticator *auth.Service) error {
|
|||||||
|
|
||||||
if s.Auth.Dev {
|
if s.Auth.Dev {
|
||||||
log.Print("[INFO] dev access enabled")
|
log.Print("[INFO] dev access enabled")
|
||||||
authenticator.AddProvider("dev", "", "")
|
u, errURL := url.Parse(s.RemarkURL)
|
||||||
|
if errURL != nil {
|
||||||
|
return fmt.Errorf("can't parse Remark42 URL: %w", errURL)
|
||||||
|
}
|
||||||
|
authenticator.AddDevProvider(u.Hostname(), 8084)
|
||||||
providersCount++
|
providersCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -871,17 +881,20 @@ func (s *ServerCommand) addAuthProviders(authenticator *auth.Service) error {
|
|||||||
SMTPUserName: s.SMTP.Username,
|
SMTPUserName: s.SMTP.Username,
|
||||||
SMTPPassword: s.SMTP.Password,
|
SMTPPassword: s.SMTP.Password,
|
||||||
TimeOut: s.SMTP.TimeOut,
|
TimeOut: s.SMTP.TimeOut,
|
||||||
|
StartTLS: s.SMTP.StartTLS,
|
||||||
|
LoginAuth: s.SMTP.LoginAuth,
|
||||||
TLS: s.SMTP.TLS,
|
TLS: s.SMTP.TLS,
|
||||||
|
Charset: "UTF-8",
|
||||||
From: s.Auth.Email.From,
|
From: s.Auth.Email.From,
|
||||||
Subject: s.Auth.Email.Subject,
|
Subject: s.Auth.Email.Subject,
|
||||||
ContentType: s.Auth.Email.ContentType,
|
ContentType: s.Auth.Email.ContentType,
|
||||||
}
|
}
|
||||||
sndr := sender.NewEmailClient(params, log.Default())
|
sndr := sender.NewEmailClient(params, log.Default())
|
||||||
tmpl, err := s.loadEmailTemplate()
|
tmpl, err := templates.Read(s.Auth.Email.MsgTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
authenticator.AddVerifProvider("email", tmpl, sndr)
|
authenticator.AddVerifProvider("email", string(tmpl), sndr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Auth.Anonymous {
|
if s.Auth.Anonymous {
|
||||||
@@ -925,27 +938,6 @@ func (s *ServerCommand) addAuthProviders(authenticator *auth.Service) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadEmailTemplate trying to get template from statik
|
|
||||||
func (s *ServerCommand) loadEmailTemplate() (string, error) {
|
|
||||||
var file []byte
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if s.Auth.Email.MsgTemplate == "email_confirmation_login.html.tmpl" {
|
|
||||||
fs := templates.NewFS()
|
|
||||||
file, err = fs.ReadFile(s.Auth.Email.MsgTemplate)
|
|
||||||
} else {
|
|
||||||
// deprecated loading from an external file, should be removed before v1.9.0
|
|
||||||
file, err = os.ReadFile(s.Auth.Email.MsgTemplate)
|
|
||||||
log.Printf("[INFO] template %s will be read from disk", s.Auth.Email.MsgTemplate)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to read file %s: %w", s.Auth.Email.MsgTemplate, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(file), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// creates and registers telegram auth, which we need separately from other auth providers
|
// creates and registers telegram auth, which we need separately from other auth providers
|
||||||
func (s *ServerCommand) makeTelegramAuth(authenticator *auth.Service) providers.TGUpdatesReceiver {
|
func (s *ServerCommand) makeTelegramAuth(authenticator *auth.Service) providers.TGUpdatesReceiver {
|
||||||
if s.Auth.Telegram {
|
if s.Auth.Telegram {
|
||||||
@@ -967,7 +959,8 @@ func (s *ServerCommand) makeNotifyService(dataStore *service.DataStore, destinat
|
|||||||
if destinations == nil {
|
if destinations == nil {
|
||||||
destinations = []notify.Destination{}
|
destinations = []notify.Destination{}
|
||||||
}
|
}
|
||||||
if telegram != nil {
|
// it's possible that telegram notification service was created for auth but should not be used for notifications
|
||||||
|
if telegram != nil && (contains("telegram", s.Notify.Users) || contains("telegram", s.Notify.Admins)) {
|
||||||
destinations = append(destinations, telegram)
|
destinations = append(destinations, telegram)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1042,6 +1035,7 @@ func (s *ServerCommand) makeNotifyDestinations(authenticator *auth.Service) ([]n
|
|||||||
Port: s.SMTP.Port,
|
Port: s.SMTP.Port,
|
||||||
TLS: s.SMTP.TLS,
|
TLS: s.SMTP.TLS,
|
||||||
StartTLS: s.SMTP.StartTLS,
|
StartTLS: s.SMTP.StartTLS,
|
||||||
|
LoginAuth: s.SMTP.LoginAuth,
|
||||||
Username: s.SMTP.Username,
|
Username: s.SMTP.Username,
|
||||||
Password: s.SMTP.Password,
|
Password: s.SMTP.Password,
|
||||||
TimeOut: s.SMTP.TimeOut,
|
TimeOut: s.SMTP.TimeOut,
|
||||||
@@ -1118,7 +1112,7 @@ func (s *ServerCommand) getAuthenticator(ds *service.DataStore, avas avatar.Stor
|
|||||||
SameSiteCookie: s.parseSameSite(s.Auth.SameSite),
|
SameSiteCookie: s.parseSameSite(s.Auth.SameSite),
|
||||||
SecureCookies: strings.HasPrefix(s.RemarkURL, "https://"),
|
SecureCookies: strings.HasPrefix(s.RemarkURL, "https://"),
|
||||||
SecretReader: token.SecretFunc(func(aud string) (string, error) { // get secret per site
|
SecretReader: token.SecretFunc(func(aud string) (string, error) { // get secret per site
|
||||||
return admns.Key("")
|
return admns.Key(aud)
|
||||||
}),
|
}),
|
||||||
ClaimsUpd: token.ClaimsUpdFunc(func(c token.Claims) token.Claims { // set attributes, on new token or refresh
|
ClaimsUpd: token.ClaimsUpdFunc(func(c token.Claims) token.Claims { // set attributes, on new token or refresh
|
||||||
if c.User == nil {
|
if c.User == nil {
|
||||||
|
|||||||
@@ -143,36 +143,42 @@ func TestServerApp_AnonMode(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
||||||
|
|
||||||
// try to login with non-latin name
|
// try to login with non-latin name
|
||||||
|
time.Sleep(time.Second)
|
||||||
resp, err = client.Get(fmt.Sprintf("http://localhost:%d/auth/anonymous/login?user=Раз_Два%20%20Три_34567&aud=remark", port))
|
resp, err = client.Get(fmt.Sprintf("http://localhost:%d/auth/anonymous/login?user=Раз_Два%20%20Три_34567&aud=remark", port))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
// try to login with bad name
|
// try to login with bad name
|
||||||
|
time.Sleep(time.Second)
|
||||||
resp, err = client.Get(fmt.Sprintf("http://localhost:%d/auth/anonymous/login?user=**blah123&aud=remark", port))
|
resp, err = client.Get(fmt.Sprintf("http://localhost:%d/auth/anonymous/login?user=**blah123&aud=remark", port))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
|
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
|
||||||
|
|
||||||
// try to login with short name
|
// try to login with short name
|
||||||
|
time.Sleep(time.Second)
|
||||||
resp, err = client.Get(fmt.Sprintf("http://localhost:%d/auth/anonymous/login?user=bl%%20%%20&aud=remark", port))
|
resp, err = client.Get(fmt.Sprintf("http://localhost:%d/auth/anonymous/login?user=bl%%20%%20&aud=remark", port))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
|
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
|
||||||
|
|
||||||
// try to login with name what have space in prefix
|
// try to login with name what have space in prefix
|
||||||
|
time.Sleep(time.Second)
|
||||||
resp, err = client.Get(fmt.Sprintf("http://localhost:%d/auth/anonymous/login?user=%%20somebody&aud=remark", port))
|
resp, err = client.Get(fmt.Sprintf("http://localhost:%d/auth/anonymous/login?user=%%20somebody&aud=remark", port))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
|
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
|
||||||
|
|
||||||
// try to login with name what have space in suffix
|
// try to login with name what have space in suffix
|
||||||
|
time.Sleep(time.Second)
|
||||||
resp, err = client.Get(fmt.Sprintf("http://localhost:%d/auth/anonymous/login?user=somebody%%20&aud=remark", port))
|
resp, err = client.Get(fmt.Sprintf("http://localhost:%d/auth/anonymous/login?user=somebody%%20&aud=remark", port))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
|
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
|
||||||
|
|
||||||
// try to login with long name
|
// try to login with long name
|
||||||
|
time.Sleep(time.Second)
|
||||||
ln := strings.Repeat("x", 65)
|
ln := strings.Repeat("x", 65)
|
||||||
resp, err = client.Get(fmt.Sprintf("http://localhost:%d/auth/anonymous/login?user=%s&aud=remark", port, ln))
|
resp, err = client.Get(fmt.Sprintf("http://localhost:%d/auth/anonymous/login?user=%s&aud=remark", port, ln))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -180,12 +186,14 @@ func TestServerApp_AnonMode(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
|
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
|
||||||
|
|
||||||
// try to login with admin name
|
// try to login with admin name
|
||||||
|
time.Sleep(time.Second)
|
||||||
resp, err = client.Get(fmt.Sprintf("http://localhost:%d/auth/anonymous/login?user=umpUtun&aud=remark", port))
|
resp, err = client.Get(fmt.Sprintf("http://localhost:%d/auth/anonymous/login?user=umpUtun&aud=remark", port))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
// try to add a comment as anonymous with admin name
|
// try to add a comment as anonymous with admin name
|
||||||
|
time.Sleep(time.Second)
|
||||||
req, err = http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/api/v1/comment", port),
|
req, err = http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/api/v1/comment", port),
|
||||||
strings.NewReader(`{"text": "test 123", "locator":{"url": "https://radio-t.com/blah1", "site": "remark"}}`))
|
strings.NewReader(`{"text": "test 123", "locator":{"url": "https://radio-t.com/blah1", "site": "remark"}}`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -659,19 +667,6 @@ func TestServerAuthHooks(t *testing.T) {
|
|||||||
client.CloseIdleConnections()
|
client.CloseIdleConnections()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_loadEmailTemplate(t *testing.T) {
|
|
||||||
cmd := ServerCommand{}
|
|
||||||
cmd.Auth.Email.MsgTemplate = "testdata/email.tmpl"
|
|
||||||
r, err := cmd.loadEmailTemplate()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, "The token is {{.Token}}", r)
|
|
||||||
|
|
||||||
cmd.Auth.Email.MsgTemplate = "badpath.tmpl"
|
|
||||||
r, err = cmd.loadEmailTemplate()
|
|
||||||
assert.EqualError(t, err, "failed to read file badpath.tmpl: open badpath.tmpl: no such file or directory")
|
|
||||||
assert.Equal(t, r, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServerCommand_parseSameSite(t *testing.T) {
|
func TestServerCommand_parseSameSite(t *testing.T) {
|
||||||
tbl := []struct {
|
tbl := []struct {
|
||||||
inp string
|
inp string
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
This stub page would be replaced by the frontend statically built HTML during the Docker image build.
|
||||||
@@ -3,7 +3,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -22,7 +21,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Test_Main(t *testing.T) {
|
func Test_Main(t *testing.T) {
|
||||||
dir, err := ioutil.TempDir(os.TempDir(), "remark42")
|
dir, err := os.MkdirTemp(os.TempDir(), "remark42")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ func Test_Main(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMain_WithWebhook(t *testing.T) {
|
func TestMain_WithWebhook(t *testing.T) {
|
||||||
dir, err := ioutil.TempDir(os.TempDir(), "remark42")
|
dir, err := os.MkdirTemp(os.TempDir(), "remark42")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ func (ab AutoBackup) removeOldBackupFiles() {
|
|||||||
backFiles = append(backFiles, info)
|
backFiles = append(backFiles, info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.Slice(backFiles, func(i int, j int) bool { return backFiles[i].Name() < backFiles[j].Name() })
|
sort.Slice(backFiles, func(i, j int) bool { return backFiles[i].Name() < backFiles[j].Name() })
|
||||||
|
|
||||||
if len(backFiles) > ab.KeepMax {
|
if len(backFiles) > ab.KeepMax {
|
||||||
for i := 0; i < len(backFiles)-ab.KeepMax; i++ {
|
for i := 0; i < len(backFiles)-ab.KeepMax; i++ {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ type MapperMaker func(reader io.Reader) (Mapper, error)
|
|||||||
type Store interface {
|
type Store interface {
|
||||||
Create(comment store.Comment) (commentID string, err error)
|
Create(comment store.Comment) (commentID string, err error)
|
||||||
Find(locator store.Locator, sort string, user store.User) ([]store.Comment, error)
|
Find(locator store.Locator, sort string, user store.User) ([]store.Comment, error)
|
||||||
List(siteID string, limit int, skip int) ([]store.PostInfo, error)
|
List(siteID string, limit, skip int) ([]store.PostInfo, error)
|
||||||
DeleteAll(siteID string) error
|
DeleteAll(siteID string) error
|
||||||
Metas(siteID string) (umetas []service.UserMetaData, pmetas []service.PostMetaData, err error)
|
Metas(siteID string) (umetas []service.UserMetaData, pmetas []service.PostMetaData, err error)
|
||||||
SetMetas(siteID string, umetas []service.UserMetaData, pmetas []service.PostMetaData) error
|
SetMetas(siteID string, umetas []service.UserMetaData, pmetas []service.PostMetaData) error
|
||||||
|
|||||||
@@ -100,7 +100,6 @@ func NewEmail(emailParams EmailParams, smtpParams ntf.SMTPParams) (*Email, error
|
|||||||
func (e *Email) setTemplates() error {
|
func (e *Email) setTemplates() error {
|
||||||
var err error
|
var err error
|
||||||
var msgTmplFile, verifyTmplFile []byte
|
var msgTmplFile, verifyTmplFile []byte
|
||||||
fs := templates.NewFS()
|
|
||||||
|
|
||||||
if e.VerificationTemplatePath == "" {
|
if e.VerificationTemplatePath == "" {
|
||||||
e.VerificationTemplatePath = defaultEmailVerificationTemplatePath
|
e.VerificationTemplatePath = defaultEmailVerificationTemplatePath
|
||||||
@@ -110,10 +109,10 @@ func (e *Email) setTemplates() error {
|
|||||||
e.MsgTemplatePath = defaultEmailTemplatePath
|
e.MsgTemplatePath = defaultEmailTemplatePath
|
||||||
}
|
}
|
||||||
|
|
||||||
if msgTmplFile, err = fs.ReadFile(e.MsgTemplatePath); err != nil {
|
if msgTmplFile, err = templates.Read(e.MsgTemplatePath); err != nil {
|
||||||
return fmt.Errorf("can't read message template: %w", err)
|
return fmt.Errorf("can't read message template: %w", err)
|
||||||
}
|
}
|
||||||
if verifyTmplFile, err = fs.ReadFile(e.VerificationTemplatePath); err != nil {
|
if verifyTmplFile, err = templates.Read(e.VerificationTemplatePath); err != nil {
|
||||||
return fmt.Errorf("can't read verification template: %w", err)
|
return fmt.Errorf("can't read verification template: %w", err)
|
||||||
}
|
}
|
||||||
if e.msgTmpl, err = template.New("msgTmpl").Parse(string(msgTmplFile)); err != nil {
|
if e.msgTmpl, err = template.New("msgTmpl").Parse(string(msgTmplFile)); err != nil {
|
||||||
|
|||||||
@@ -55,32 +55,18 @@ func Test_initTemplatesErr(t *testing.T) {
|
|||||||
errText string
|
errText string
|
||||||
emailParams EmailParams
|
emailParams EmailParams
|
||||||
}{
|
}{
|
||||||
{
|
|
||||||
name: "with wrong (default, working in prod) path to reply template",
|
|
||||||
errText: "can't read message template: open email_reply.html.tmpl: no such file or directory",
|
|
||||||
emailParams: EmailParams{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "with wrong (default, working in prod) path to verification template",
|
|
||||||
errText: "can't read verification template: open email_confirmation_subscription.html.tmpl: no such file or directory",
|
|
||||||
emailParams: EmailParams{
|
|
||||||
MsgTemplatePath: "testdata/msg.html.tmpl",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "with wrong path to verification template",
|
name: "with wrong path to verification template",
|
||||||
errText: "can't read verification template: open notfound.tmpl: no such file or directory",
|
errText: "notfound.tmpl: file does not exist",
|
||||||
emailParams: EmailParams{
|
emailParams: EmailParams{
|
||||||
VerificationTemplatePath: "notfound.tmpl",
|
VerificationTemplatePath: "notfound.tmpl",
|
||||||
MsgTemplatePath: "testdata/msg.html.tmpl",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "with wrong path to message template",
|
name: "with wrong path to message template",
|
||||||
errText: "can't read message template: open notfound.tmpl: no such file or directory",
|
errText: "notfound.tmpl: file does not exist",
|
||||||
emailParams: EmailParams{
|
emailParams: EmailParams{
|
||||||
VerificationTemplatePath: "testdata/verification.html.tmpl",
|
MsgTemplatePath: "notfound.tmpl",
|
||||||
MsgTemplatePath: "notfound.tmpl",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ type Destination interface {
|
|||||||
// Store defines the minimal interface accessing stored comments used by notifier
|
// Store defines the minimal interface accessing stored comments used by notifier
|
||||||
type Store interface {
|
type Store interface {
|
||||||
Get(locator store.Locator, id string, user store.User) (store.Comment, error)
|
Get(locator store.Locator, id string, user store.User) (store.Comment, error)
|
||||||
GetUserEmail(siteID string, userID string) (string, error)
|
GetUserEmail(siteID, userID string) (string, error)
|
||||||
GetUserTelegram(siteID string, userID string) (string, error)
|
GetUserTelegram(siteID, userID string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// used for email and telegram retrieval from user details
|
// used for email and telegram retrieval from user details
|
||||||
|
|||||||
@@ -29,15 +29,15 @@ type admin struct {
|
|||||||
|
|
||||||
type adminStore interface {
|
type adminStore interface {
|
||||||
Delete(locator store.Locator, commentID string, mode store.DeleteMode) error
|
Delete(locator store.Locator, commentID string, mode store.DeleteMode) error
|
||||||
DeleteUser(siteID string, userID string, mode store.DeleteMode) error
|
DeleteUser(siteID, userID string, mode store.DeleteMode) error
|
||||||
DeleteUserDetail(siteID string, userID string, detail engine.UserDetail) error
|
DeleteUserDetail(siteID, userID string, detail engine.UserDetail) error
|
||||||
User(siteID, userID string, limit, skip int, user store.User) ([]store.Comment, error)
|
User(siteID, userID string, limit, skip int, user store.User) ([]store.Comment, error)
|
||||||
IsBlocked(siteID string, userID string) bool
|
IsBlocked(siteID, userID string) bool
|
||||||
SetBlock(siteID string, userID string, status bool, ttl time.Duration) error
|
SetBlock(siteID, userID string, status bool, ttl time.Duration) error
|
||||||
BlockedUsers(siteID string) ([]store.BlockedUser, error)
|
BlockedUsers(siteID string) ([]store.BlockedUser, error)
|
||||||
Info(locator store.Locator, readonlyAge int) (store.PostInfo, error)
|
Info(locator store.Locator, readonlyAge int) (store.PostInfo, error)
|
||||||
SetTitle(locator store.Locator, commentID string) (comment store.Comment, err error)
|
SetTitle(locator store.Locator, commentID string) (comment store.Comment, err error)
|
||||||
SetVerified(siteID string, userID string, status bool) error
|
SetVerified(siteID, userID string, status bool) error
|
||||||
SetReadOnly(locator store.Locator, status bool) error
|
SetReadOnly(locator store.Locator, status bool) error
|
||||||
SetPin(locator store.Locator, commentID string, status bool) error
|
SetPin(locator store.Locator, commentID string, status bool) error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -172,7 +171,7 @@ func (m *Migrator) remapCtrl(w http.ResponseWriter, r *http.Request) {
|
|||||||
defer m.setBusy(siteID, false)
|
defer m.setBusy(siteID, false)
|
||||||
|
|
||||||
// do export
|
// do export
|
||||||
fh, e := ioutil.TempFile("", "remark42_convert")
|
fh, e := os.CreateTemp("", "remark42_convert")
|
||||||
if e != nil {
|
if e != nil {
|
||||||
log.Printf("[WARN] failed to make temp file %+v", e)
|
log.Printf("[WARN] failed to make temp file %+v", e)
|
||||||
return
|
return
|
||||||
@@ -250,7 +249,7 @@ func (m *Migrator) runImport(siteID, provider, tmpfile string) {
|
|||||||
|
|
||||||
// saveTemp reads from reader and saves to temp file
|
// saveTemp reads from reader and saves to temp file
|
||||||
func (m *Migrator) saveTemp(r io.Reader) (string, error) {
|
func (m *Migrator) saveTemp(r io.Reader) (string, error) {
|
||||||
tmpfile, err := ioutil.TempFile("", "remark42_import")
|
tmpfile, err := os.CreateTemp("", "remark42_import")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("can't make temp file: %w", err)
|
return "", fmt.Errorf("can't make temp file: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,19 @@ package api
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"embed"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/mail"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/didip/tollbooth/v6"
|
"github.com/didip/tollbooth/v7"
|
||||||
"github.com/didip/tollbooth_chi"
|
"github.com/didip/tollbooth_chi"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
@@ -21,7 +26,6 @@ import (
|
|||||||
log "github.com/go-pkgz/lgr"
|
log "github.com/go-pkgz/lgr"
|
||||||
R "github.com/go-pkgz/rest"
|
R "github.com/go-pkgz/rest"
|
||||||
"github.com/go-pkgz/rest/logger"
|
"github.com/go-pkgz/rest/logger"
|
||||||
"github.com/rakyll/statik/fs"
|
|
||||||
|
|
||||||
"github.com/umputun/remark42/backend/app/notify"
|
"github.com/umputun/remark42/backend/app/notify"
|
||||||
"github.com/umputun/remark42/backend/app/rest"
|
"github.com/umputun/remark42/backend/app/rest"
|
||||||
@@ -29,7 +33,6 @@ import (
|
|||||||
"github.com/umputun/remark42/backend/app/store"
|
"github.com/umputun/remark42/backend/app/store"
|
||||||
"github.com/umputun/remark42/backend/app/store/image"
|
"github.com/umputun/remark42/backend/app/store/image"
|
||||||
"github.com/umputun/remark42/backend/app/store/service"
|
"github.com/umputun/remark42/backend/app/store/service"
|
||||||
"github.com/umputun/remark42/backend/app/templates"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Rest is a rest access server
|
// Rest is a rest access server
|
||||||
@@ -48,6 +51,7 @@ type Rest struct {
|
|||||||
|
|
||||||
AnonVote bool
|
AnonVote bool
|
||||||
WebRoot string
|
WebRoot string
|
||||||
|
WebFS embed.FS
|
||||||
RemarkURL string
|
RemarkURL string
|
||||||
ReadOnlyAge int
|
ReadOnlyAge int
|
||||||
SharedSecret string
|
SharedSecret string
|
||||||
@@ -223,7 +227,8 @@ func (s *Rest) routes() chi.Router {
|
|||||||
|
|
||||||
router.Group(func(r chi.Router) {
|
router.Group(func(r chi.Router) {
|
||||||
r.Use(middleware.Timeout(5 * time.Second))
|
r.Use(middleware.Timeout(5 * time.Second))
|
||||||
r.Use(logInfoWithBody, tollbooth_chi.LimitHandler(tollbooth.NewLimiter(10, nil)), middleware.NoCache)
|
r.Use(logInfoWithBody, tollbooth_chi.LimitHandler(tollbooth.NewLimiter(2, nil)), middleware.NoCache)
|
||||||
|
r.Use(validEmailAuth()) // reject suspicious email logins
|
||||||
r.Mount("/auth", authHandler)
|
r.Mount("/auth", authHandler)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -344,14 +349,13 @@ func (s *Rest) routes() chi.Router {
|
|||||||
router.Group(func(rroot chi.Router) {
|
router.Group(func(rroot chi.Router) {
|
||||||
rroot.Use(middleware.Timeout(10 * time.Second))
|
rroot.Use(middleware.Timeout(10 * time.Second))
|
||||||
rroot.Use(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(50, nil)))
|
rroot.Use(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(50, nil)))
|
||||||
rroot.Get("/index.html", s.pubRest.getStartedCtrl)
|
|
||||||
rroot.Get("/robots.txt", s.pubRest.robotsCtrl)
|
rroot.Get("/robots.txt", s.pubRest.robotsCtrl)
|
||||||
rroot.Get("/email/unsubscribe.html", s.privRest.emailUnsubscribeCtrl)
|
rroot.Get("/email/unsubscribe.html", s.privRest.emailUnsubscribeCtrl)
|
||||||
rroot.Post("/email/unsubscribe.html", s.privRest.emailUnsubscribeCtrl)
|
rroot.Post("/email/unsubscribe.html", s.privRest.emailUnsubscribeCtrl)
|
||||||
})
|
})
|
||||||
|
|
||||||
// file server for static content from /web
|
// file server for static content from s.WebRoot on path /web
|
||||||
addFileServer(router, "/web", http.Dir(s.WebRoot), s.Version)
|
addFileServer(router, s.WebFS, s.WebRoot, s.Version)
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,7 +366,6 @@ func (s *Rest) controllerGroups() (public, private, admin, rss) {
|
|||||||
imageService: s.ImageService,
|
imageService: s.ImageService,
|
||||||
commentFormatter: s.CommentFormatter,
|
commentFormatter: s.CommentFormatter,
|
||||||
readOnlyAge: s.ReadOnlyAge,
|
readOnlyAge: s.ReadOnlyAge,
|
||||||
webRoot: s.WebRoot,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
privGrp := private{
|
privGrp := private{
|
||||||
@@ -376,7 +379,6 @@ func (s *Rest) controllerGroups() (public, private, admin, rss) {
|
|||||||
telegramService: s.TelegramService,
|
telegramService: s.TelegramService,
|
||||||
remarkURL: s.RemarkURL,
|
remarkURL: s.RemarkURL,
|
||||||
anonVote: s.AnonVote,
|
anonVote: s.AnonVote,
|
||||||
templates: templates.NewFS(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
admGrp := admin{
|
admGrp := admin{
|
||||||
@@ -464,34 +466,28 @@ func (s *Rest) configCtrl(w http.ResponseWriter, r *http.Request) {
|
|||||||
render.JSON(w, r, cnf)
|
render.JSON(w, r, cnf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// serves static files from /web or embedded by statik
|
// serves static files from the webRoot directory or files embedded into the compiled binary if that directory is absent
|
||||||
func addFileServer(r chi.Router, path string, root http.FileSystem, version string) {
|
func addFileServer(r chi.Router, embedFS embed.FS, webRoot, version string) {
|
||||||
var webFS http.Handler
|
var webFS http.Handler
|
||||||
|
|
||||||
statikFS, err := fs.New()
|
if _, err := os.Stat(webRoot); err == nil {
|
||||||
if err != nil {
|
log.Printf("[INFO] run file server from %s from the disk", webRoot)
|
||||||
log.Printf("[DEBUG] no embedded assets loaded, %s", err)
|
webFS = http.FileServer(http.Dir(webRoot))
|
||||||
log.Printf("[INFO] run file server for %s, path %s", root, path)
|
|
||||||
webFS = http.FileServer(root)
|
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[INFO] run file server for %s, embedded", root)
|
log.Printf("[INFO] run file server, embedded")
|
||||||
webFS = http.FileServer(statikFS)
|
var contentFS, _ = fs.Sub(embedFS, "web")
|
||||||
|
webFS = http.FileServer(http.FS(contentFS))
|
||||||
}
|
}
|
||||||
|
|
||||||
origPath := path
|
webFS = http.StripPrefix("/web", webFS)
|
||||||
webFS = http.StripPrefix(path, webFS)
|
r.Get("/web", http.RedirectHandler("/web/", http.StatusMovedPermanently).ServeHTTP)
|
||||||
if path != "/" && path[len(path)-1] != '/' {
|
|
||||||
r.Get(path, http.RedirectHandler(path+"/", http.StatusMovedPermanently).ServeHTTP)
|
|
||||||
path += "/"
|
|
||||||
}
|
|
||||||
path += "*"
|
|
||||||
|
|
||||||
r.With(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(20, nil)),
|
r.With(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(20, nil)),
|
||||||
middleware.Timeout(10*time.Second),
|
middleware.Timeout(10*time.Second),
|
||||||
cacheControl(time.Hour, version),
|
cacheControl(time.Hour, version),
|
||||||
).Get(path, func(w http.ResponseWriter, r *http.Request) {
|
).Get("/web/*", func(w http.ResponseWriter, r *http.Request) {
|
||||||
// don't show dirs, just serve files
|
// don't show dirs, just serve files
|
||||||
if strings.HasSuffix(r.URL.Path, "/") && len(r.URL.Path) > 1 && r.URL.Path != (origPath+"/") {
|
if strings.HasSuffix(r.URL.Path, "/") && len(r.URL.Path) > 1 && r.URL.Path != ("/web/") {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -650,6 +646,52 @@ func subscribersOnly(enable bool) func(http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validEmailAuth is a middleware for auth endpoints for email method.
|
||||||
|
// it rejects login request if user, site or email are suspicious
|
||||||
|
func validEmailAuth() func(http.Handler) http.Handler {
|
||||||
|
|
||||||
|
reUser := regexp.MustCompile(`^[\p{L}\d\s_]{4,64}$`) // matches ui side validation, adding min/max limitation
|
||||||
|
reSite := regexp.MustCompile(`^[a-zA-Z\d\s_.-]{1,64}$`)
|
||||||
|
|
||||||
|
return func(h http.Handler) http.Handler {
|
||||||
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
if r.URL.Path != "/auth/email/login" {
|
||||||
|
// not email login, skip the check
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if u := r.URL.Query().Get("user"); u != "" {
|
||||||
|
if !reUser.MatchString(u) {
|
||||||
|
log.Printf("[WARN] suspicious user rejected: %s", u)
|
||||||
|
http.Error(w, "Access denied", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if a := r.URL.Query().Get("address"); a != "" {
|
||||||
|
if _, err := mail.ParseAddress(a); err != nil {
|
||||||
|
log.Printf("[WARN] suspicious address rejected: %s", a)
|
||||||
|
http.Error(w, "Access denied", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s := r.URL.Query().Get("site"); s != "" {
|
||||||
|
if !reSite.MatchString(s) {
|
||||||
|
log.Printf("[WARN] suspicious site rejected: %s", s)
|
||||||
|
http.Error(w, "Access denied", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
return http.HandlerFunc(fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func parseError(err error, defaultCode int) (code int) {
|
func parseError(err error, defaultCode int) (code int) {
|
||||||
code = defaultCode
|
code = defaultCode
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ type private struct {
|
|||||||
telegramService telegramService
|
telegramService telegramService
|
||||||
remarkURL string
|
remarkURL string
|
||||||
anonVote bool
|
anonVote bool
|
||||||
templates templates.FileReader
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// telegramService is a subset of Telegram service used for setting up user telegram notifications
|
// telegramService is a subset of Telegram service used for setting up user telegram notifications
|
||||||
@@ -59,15 +58,15 @@ type privStore interface {
|
|||||||
Vote(req service.VoteReq) (comment store.Comment, err error)
|
Vote(req service.VoteReq) (comment store.Comment, err error)
|
||||||
Get(locator store.Locator, commentID string, user store.User) (store.Comment, error)
|
Get(locator store.Locator, commentID string, user store.User) (store.Comment, error)
|
||||||
User(siteID, userID string, limit, skip int, user store.User) ([]store.Comment, error)
|
User(siteID, userID string, limit, skip int, user store.User) ([]store.Comment, error)
|
||||||
GetUserEmail(siteID string, userID string) (string, error)
|
GetUserEmail(siteID, userID string) (string, error)
|
||||||
SetUserEmail(siteID string, userID string, value string) (string, error)
|
SetUserEmail(siteID, userID, value string) (string, error)
|
||||||
GetUserTelegram(siteID string, userID string) (string, error)
|
GetUserTelegram(siteID, userID string) (string, error)
|
||||||
SetUserTelegram(siteID string, userID string, value string) (string, error)
|
SetUserTelegram(siteID, userID, value string) (string, error)
|
||||||
DeleteUserDetail(siteID string, userID string, detail engine.UserDetail) error
|
DeleteUserDetail(siteID, userID string, detail engine.UserDetail) error
|
||||||
ValidateComment(c *store.Comment) error
|
ValidateComment(c *store.Comment) error
|
||||||
IsVerified(siteID string, userID string) bool
|
IsVerified(siteID, userID string) bool
|
||||||
IsReadOnly(locator store.Locator) bool
|
IsReadOnly(locator store.Locator) bool
|
||||||
IsBlocked(siteID string, userID string) bool
|
IsBlocked(siteID, userID string) bool
|
||||||
Info(locator store.Locator, readonlyAge int) (store.PostInfo, error)
|
Info(locator store.Locator, readonlyAge int) (store.PostInfo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +172,7 @@ func (s *private) createCommentCtrl(w http.ResponseWriter, r *http.Request) {
|
|||||||
s.notifyService.Submit(notify.Request{Comment: finalComment})
|
s.notifyService.Submit(notify.Request{Comment: finalComment})
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[DEBUG] created commend %+v", finalComment)
|
log.Printf("[DEBUG] created comment %+v", finalComment)
|
||||||
|
|
||||||
render.Status(r, http.StatusCreated)
|
render.Status(r, http.StatusCreated)
|
||||||
render.JSON(w, r, &finalComment)
|
render.JSON(w, r, &finalComment)
|
||||||
@@ -473,29 +472,26 @@ func (s *private) setConfirmedEmailCtrl(w http.ResponseWriter, r *http.Request)
|
|||||||
func (s *private) emailUnsubscribeCtrl(w http.ResponseWriter, r *http.Request) {
|
func (s *private) emailUnsubscribeCtrl(w http.ResponseWriter, r *http.Request) {
|
||||||
tkn := r.URL.Query().Get("tkn")
|
tkn := r.URL.Query().Get("tkn")
|
||||||
if tkn == "" {
|
if tkn == "" {
|
||||||
rest.SendErrorHTML(w, r, http.StatusBadRequest,
|
rest.SendErrorHTML(w, r, http.StatusBadRequest, fmt.Errorf("missing parameter"), "token parameter is required", rest.ErrInternal)
|
||||||
fmt.Errorf("missing parameter"), "token parameter is required", rest.ErrInternal, s.templates)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
siteID := r.URL.Query().Get("site")
|
siteID := r.URL.Query().Get("site")
|
||||||
|
|
||||||
confClaims, err := s.authenticator.TokenService().Parse(tkn)
|
confClaims, err := s.authenticator.TokenService().Parse(tkn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rest.SendErrorHTML(w, r, http.StatusForbidden, err, "failed to verify confirmation token", rest.ErrInternal, s.templates)
|
rest.SendErrorHTML(w, r, http.StatusForbidden, err, "failed to verify confirmation token", rest.ErrInternal)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.authenticator.TokenService().IsExpired(confClaims) {
|
if s.authenticator.TokenService().IsExpired(confClaims) {
|
||||||
rest.SendErrorHTML(w, r, http.StatusForbidden,
|
rest.SendErrorHTML(w, r, http.StatusForbidden, fmt.Errorf("expired"), "failed to verify confirmation token", rest.ErrInternal)
|
||||||
fmt.Errorf("expired"), "failed to verify confirmation token", rest.ErrInternal, s.templates)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handshake.ID is user.ID + "::" + address
|
// Handshake.ID is user.ID + "::" + address
|
||||||
elems := strings.Split(confClaims.Handshake.ID, "::")
|
elems := strings.Split(confClaims.Handshake.ID, "::")
|
||||||
if len(elems) != 2 {
|
if len(elems) != 2 {
|
||||||
rest.SendErrorHTML(w, r, http.StatusBadRequest,
|
rest.SendErrorHTML(w, r, http.StatusBadRequest, fmt.Errorf("%s", confClaims.Handshake.ID), "invalid handshake token", rest.ErrInternal)
|
||||||
fmt.Errorf("%s", confClaims.Handshake.ID), "invalid handshake token", rest.ErrInternal, s.templates)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userID := elems[0]
|
userID := elems[0]
|
||||||
@@ -508,14 +504,11 @@ func (s *private) emailUnsubscribeCtrl(w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Printf("[WARN] can't read email for %s, %v", userID, err)
|
log.Printf("[WARN] can't read email for %s, %v", userID, err)
|
||||||
}
|
}
|
||||||
if existingAddress == "" {
|
if existingAddress == "" {
|
||||||
rest.SendErrorHTML(w, r, http.StatusConflict,
|
rest.SendErrorHTML(w, r, http.StatusConflict, fmt.Errorf("user is not subscribed"), "user does not have active email subscription", rest.ErrInternal)
|
||||||
fmt.Errorf("user is not subscribed"), "user does not have active email subscription", rest.ErrInternal, s.templates)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if address != existingAddress {
|
if address != existingAddress {
|
||||||
rest.SendErrorHTML(w, r, http.StatusBadRequest,
|
rest.SendErrorHTML(w, r, http.StatusBadRequest, fmt.Errorf("wrong email unsubscription"), "email address in request does not match known for this user", rest.ErrInternal)
|
||||||
fmt.Errorf("wrong email unsubscription"), "email address in request does not match known for this user",
|
|
||||||
rest.ErrInternal, s.templates)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,7 +516,7 @@ func (s *private) emailUnsubscribeCtrl(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if err = s.dataService.DeleteUserDetail(siteID, userID, engine.UserEmail); err != nil {
|
if err = s.dataService.DeleteUserDetail(siteID, userID, engine.UserEmail); err != nil {
|
||||||
code := parseError(err, rest.ErrInternal)
|
code := parseError(err, rest.ErrInternal)
|
||||||
rest.SendErrorHTML(w, r, http.StatusBadRequest, err, "can't delete email for user", code, s.templates)
|
rest.SendErrorHTML(w, r, http.StatusBadRequest, err, "can't delete email for user", code)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// clean User.Email from the token, if user has the token
|
// clean User.Email from the token, if user has the token
|
||||||
@@ -534,7 +527,7 @@ func (s *private) emailUnsubscribeCtrl(w http.ResponseWriter, r *http.Request) {
|
|||||||
if claims.User != nil && claims.User.Email != "" {
|
if claims.User != nil && claims.User.Email != "" {
|
||||||
claims.User.Email = ""
|
claims.User.Email = ""
|
||||||
if _, err = s.authenticator.TokenService().Set(w, claims); err != nil {
|
if _, err = s.authenticator.TokenService().Set(w, claims); err != nil {
|
||||||
rest.SendErrorHTML(w, r, http.StatusInternalServerError, err, "failed to set token", rest.ErrInternal, s.templates)
|
rest.SendErrorHTML(w, r, http.StatusInternalServerError, err, "failed to set token", rest.ErrInternal)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -546,13 +539,13 @@ func (s *private) emailUnsubscribeCtrl(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
MustRead := func(path string) string {
|
MustRead := func(path string) string {
|
||||||
file, err := s.templates.ReadFile(path)
|
file, err := templates.Read(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return string(file)
|
return string(file)
|
||||||
}
|
}
|
||||||
tmplstr := MustRead("unsubscribe.html.tmpl")
|
tmplstr := MustRead("email_unsubscribe.html.tmpl")
|
||||||
tmpl := template.Must(template.New("unsubscribe").Parse(tmplstr))
|
tmpl := template.Must(template.New("unsubscribe").Parse(tmplstr))
|
||||||
msg := bytes.Buffer{}
|
msg := bytes.Buffer{}
|
||||||
MustExecute(tmpl, &msg, nil)
|
MustExecute(tmpl, &msg, nil)
|
||||||
|
|||||||
@@ -54,6 +54,31 @@ func TestRest_Create(t *testing.T) {
|
|||||||
assert.True(t, len(c["id"].(string)) > 8)
|
assert.True(t, len(c["id"].(string)) > 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// based on issue https://github.com/umputun/remark42/issues/1292
|
||||||
|
func TestRest_CreateFilteredCode(t *testing.T) {
|
||||||
|
ts, _, teardown := startupT(t)
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
resp, err := post(t, ts.URL+"/api/v1/comment",
|
||||||
|
`{"text": "`+"`foo<bar>`"+`", "locator":{"url": "https://radio-t.com/blah1", "site": "remark42"}}`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
b, err := io.ReadAll(resp.Body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
require.Equal(t, http.StatusCreated, resp.StatusCode, string(b))
|
||||||
|
assert.NoError(t, resp.Body.Close())
|
||||||
|
|
||||||
|
c := R.JSON{}
|
||||||
|
err = json.Unmarshal(b, &c)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
loc := c["locator"].(map[string]interface{})
|
||||||
|
assert.Equal(t, "remark42", loc["site"])
|
||||||
|
assert.Equal(t, "https://radio-t.com/blah1", loc["url"])
|
||||||
|
assert.Equal(t, "`foo<bar>`", c["orig"])
|
||||||
|
assert.Contains(t, c["text"], "foo")
|
||||||
|
assert.Contains(t, c["text"], "bar")
|
||||||
|
assert.True(t, len(c["id"].(string)) > 8)
|
||||||
|
}
|
||||||
|
|
||||||
func TestRest_CreateOldPost(t *testing.T) {
|
func TestRest_CreateOldPost(t *testing.T) {
|
||||||
ts, srv, teardown := startupT(t)
|
ts, srv, teardown := startupT(t)
|
||||||
defer teardown()
|
defer teardown()
|
||||||
@@ -348,6 +373,109 @@ func TestRest_UpdateDelete(t *testing.T) {
|
|||||||
{URL: "https://radio-t.com/blah2", Count: 0}}, j)
|
{URL: "https://radio-t.com/blah2", Count: 0}}, j)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRest_DeleteChildThenParent(t *testing.T) {
|
||||||
|
ts, _, teardown := startupT(t)
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
p := store.Comment{Text: "test test #1",
|
||||||
|
Locator: store.Locator{SiteID: "remark42", URL: "https://radio-t.com/blah1"}}
|
||||||
|
idP := addComment(t, p, ts)
|
||||||
|
|
||||||
|
c1 := store.Comment{Text: "test test #1", ParentID: idP,
|
||||||
|
Locator: store.Locator{SiteID: "remark42", URL: "https://radio-t.com/blah1"}}
|
||||||
|
idC1 := addComment(t, c1, ts)
|
||||||
|
|
||||||
|
c2 := store.Comment{Text: "test test #1", ParentID: idP,
|
||||||
|
Locator: store.Locator{SiteID: "remark42", URL: "https://radio-t.com/blah1"}}
|
||||||
|
idC2 := addComment(t, c2, ts)
|
||||||
|
|
||||||
|
// check multi count equals two
|
||||||
|
resp, err := post(t, ts.URL+"/api/v1/counts?site=remark42", `["https://radio-t.com/blah1"]`)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
bb, err := io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NoError(t, resp.Body.Close())
|
||||||
|
j := []store.PostInfo{}
|
||||||
|
err = json.Unmarshal(bb, &j)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []store.PostInfo{{URL: "https://radio-t.com/blah1", Count: 3}}, j)
|
||||||
|
|
||||||
|
// update a parent comment fails after child is created
|
||||||
|
client := http.Client{}
|
||||||
|
defer client.CloseIdleConnections()
|
||||||
|
req, err := http.NewRequest(http.MethodPut, ts.URL+"/api/v1/comment/"+idP+"?site=remark42&url=https://radio-t.com/blah1",
|
||||||
|
strings.NewReader(`{"text": "updated text", "summary":"updated by user"}`))
|
||||||
|
require.NoError(t, err)
|
||||||
|
req.Header.Add("X-JWT", devToken)
|
||||||
|
b, err := client.Do(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
body, err := io.ReadAll(b.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusBadRequest, b.StatusCode, string(body))
|
||||||
|
assert.NoError(t, b.Body.Close())
|
||||||
|
|
||||||
|
// delete first child comment
|
||||||
|
req, err = http.NewRequest(http.MethodPut, ts.URL+"/api/v1/comment/"+idC1+"?site=remark42&url=https://radio-t.com/blah1",
|
||||||
|
strings.NewReader(`{"delete": true, "summary":"removed by user"}`))
|
||||||
|
require.NoError(t, err)
|
||||||
|
req.Header.Add("X-JWT", devToken)
|
||||||
|
b, err = client.Do(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
body, err = io.ReadAll(b.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, b.StatusCode, string(body))
|
||||||
|
assert.NoError(t, b.Body.Close())
|
||||||
|
|
||||||
|
// delete a parent comment, fails as one comment child still present
|
||||||
|
defer client.CloseIdleConnections()
|
||||||
|
req, err = http.NewRequest(http.MethodPut, ts.URL+"/api/v1/comment/"+idP+"?site=remark42&url=https://radio-t.com/blah1",
|
||||||
|
strings.NewReader(`{"delete": true, "summary":"removed by user"}`))
|
||||||
|
require.NoError(t, err)
|
||||||
|
req.Header.Add("X-JWT", devToken)
|
||||||
|
b, err = client.Do(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
body, err = io.ReadAll(b.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusBadRequest, b.StatusCode, string(body))
|
||||||
|
assert.NoError(t, b.Body.Close())
|
||||||
|
|
||||||
|
// delete second child comment, as an admin to check both deletion methods
|
||||||
|
req, err = http.NewRequest(http.MethodDelete,
|
||||||
|
fmt.Sprintf("%s/api/v1/admin/comment/%s?site=remark42&url=https://radio-t.com/blah1", ts.URL, idC2), http.NoBody)
|
||||||
|
require.NoError(t, err)
|
||||||
|
requireAdminOnly(t, req)
|
||||||
|
resp, err = sendReq(t, req, adminUmputunToken)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, resp.Body.Close())
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
|
// delete a parent comment, shouldn't fail after children are deleted
|
||||||
|
defer client.CloseIdleConnections()
|
||||||
|
req, err = http.NewRequest(http.MethodPut, ts.URL+"/api/v1/comment/"+idP+"?site=remark42&url=https://radio-t.com/blah1",
|
||||||
|
strings.NewReader(`{"delete": true, "summary":"removed by user"}`))
|
||||||
|
require.NoError(t, err)
|
||||||
|
req.Header.Add("X-JWT", devToken)
|
||||||
|
b, err = client.Do(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
body, err = io.ReadAll(b.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, b.StatusCode, string(body))
|
||||||
|
assert.NoError(t, b.Body.Close())
|
||||||
|
|
||||||
|
// check multi count decremented to zero
|
||||||
|
resp, err = post(t, ts.URL+"/api/v1/counts?site=remark42", `["https://radio-t.com/blah1"]`)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
bb, err = io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NoError(t, resp.Body.Close())
|
||||||
|
j = []store.PostInfo{}
|
||||||
|
err = json.Unmarshal(bb, &j)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []store.PostInfo{{URL: "https://radio-t.com/blah1", Count: 0}}, j)
|
||||||
|
}
|
||||||
|
|
||||||
func TestRest_UpdateNotOwner(t *testing.T) {
|
func TestRest_UpdateNotOwner(t *testing.T) {
|
||||||
ts, srv, teardown := startupT(t)
|
ts, srv, teardown := startupT(t)
|
||||||
defer teardown()
|
defer teardown()
|
||||||
@@ -371,8 +499,6 @@ func TestRest_UpdateNotOwner(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusForbidden, b.StatusCode, string(body), "update from non-owner")
|
assert.Equal(t, http.StatusForbidden, b.StatusCode, string(body), "update from non-owner")
|
||||||
assert.Equal(t, `{"code":3,"details":"can not edit comments for other users","error":"rejected"}`+"\n", string(body))
|
assert.Equal(t, `{"code":3,"details":"can not edit comments for other users","error":"rejected"}`+"\n", string(body))
|
||||||
|
|
||||||
client = http.Client{}
|
|
||||||
defer client.CloseIdleConnections()
|
|
||||||
req, err = http.NewRequest(http.MethodPut, ts.URL+"/api/v1/comment/"+id1+
|
req, err = http.NewRequest(http.MethodPut, ts.URL+"/api/v1/comment/"+id1+
|
||||||
"?site=remark42&url=https://radio-t.com/blah1", strings.NewReader(`ERRR "text":"updated text", "summary":"my"}`))
|
"?site=remark42&url=https://radio-t.com/blah1", strings.NewReader(`ERRR "text":"updated text", "summary":"my"}`))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -575,17 +701,10 @@ func TestRest_AnonVote(t *testing.T) {
|
|||||||
assert.Equal(t, map[string]store.VotedIPInfo(nil), cr.VotedIPs)
|
assert.Equal(t, map[string]store.VotedIPInfo(nil), cr.VotedIPs)
|
||||||
}
|
}
|
||||||
|
|
||||||
type MockFS struct{}
|
|
||||||
|
|
||||||
func (fs *MockFS) ReadFile(path string) ([]byte, error) {
|
|
||||||
return []byte(fmt.Sprintf("template %s", path)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRest_EmailAndTelegram(t *testing.T) {
|
func TestRest_EmailAndTelegram(t *testing.T) {
|
||||||
ts, srv, teardown := startupT(t)
|
ts, srv, teardown := startupT(t)
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
|
||||||
srv.privRest.templates = &MockFS{}
|
|
||||||
srv.privRest.telegramService = &mockTelegram{site: "remark42"}
|
srv.privRest.telegramService = &mockTelegram{site: "remark42"}
|
||||||
|
|
||||||
// issue good token
|
// issue good token
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -32,7 +30,6 @@ type public struct {
|
|||||||
readOnlyAge int
|
readOnlyAge int
|
||||||
commentFormatter *store.CommentFormatter
|
commentFormatter *store.CommentFormatter
|
||||||
imageService *image.Service
|
imageService *image.Service
|
||||||
webRoot string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type pubStore interface {
|
type pubStore interface {
|
||||||
@@ -43,7 +40,7 @@ type pubStore interface {
|
|||||||
User(siteID, userID string, limit, skip int, user store.User) ([]store.Comment, error)
|
User(siteID, userID string, limit, skip int, user store.User) ([]store.Comment, error)
|
||||||
UserCount(siteID, userID string) (int, error)
|
UserCount(siteID, userID string) (int, error)
|
||||||
Count(locator store.Locator) (int, error)
|
Count(locator store.Locator) (int, error)
|
||||||
List(siteID string, limit int, skip int) ([]store.PostInfo, error)
|
List(siteID string, limit, skip int) ([]store.PostInfo, error)
|
||||||
Info(locator store.Locator, readonlyAge int) (store.PostInfo, error)
|
Info(locator store.Locator, readonlyAge int) (store.PostInfo, error)
|
||||||
|
|
||||||
ValidateComment(c *store.Comment) error
|
ValidateComment(c *store.Comment) error
|
||||||
@@ -345,16 +342,6 @@ func (s *public) loadPictureCtrl(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /index.html - respond to /index.html with the content of getstarted.html under /web root
|
|
||||||
func (s *public) getStartedCtrl(w http.ResponseWriter, r *http.Request) {
|
|
||||||
data, err := os.ReadFile(path.Join(s.webRoot, "getstarted.html"))
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
render.HTML(w, r, string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET /robots.txt
|
// GET /robots.txt
|
||||||
func (s *public) robotsCtrl(w http.ResponseWriter, r *http.Request) {
|
func (s *public) robotsCtrl(w http.ResponseWriter, r *http.Request) {
|
||||||
allowed := []string{"/find", "/last", "/id", "/count", "/counts", "/list", "/config", "/user",
|
allowed := []string{"/find", "/last", "/id", "/count", "/counts", "/list", "/config", "/user",
|
||||||
|
|||||||
@@ -131,10 +131,10 @@ BKT
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
`<h1>h1</h1>
|
`<h1>h1</h1>
|
||||||
<pre class="chroma"><code><span><span>func TestRest_Preview(t *testing.T) {
|
<pre class="chroma"><code><span class="line"><span class="cl">func TestRest_Preview(t *testing.T) {
|
||||||
</span></span><span><span>srv, ts := prep(t)
|
</span></span><span class="line"><span class="cl">srv, ts := prep(t)
|
||||||
</span></span><span><span> require.NotNil(t, srv)
|
</span></span><span class="line"><span class="cl"> require.NotNil(t, srv)
|
||||||
</span></span><span><span>}
|
</span></span><span class="line"><span class="cl">}
|
||||||
</span></span></code></pre>`,
|
</span></span></code></pre>`,
|
||||||
string(b))
|
string(b))
|
||||||
assert.NoError(t, resp.Body.Close())
|
assert.NoError(t, resp.Body.Close())
|
||||||
@@ -157,7 +157,7 @@ BKT
|
|||||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
b, err := io.ReadAll(resp.Body)
|
b, err := io.ReadAll(resp.Body)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, `<pre class="chroma"><code><span><span><span class="kd">func</span> <span class="nf">main</span><span class="p">(</span><span class="nx">aa</span> <span class="kt">string</span><span class="p">)</span> <span class="kt">int</span> <span class="p">{</span><span class="k">return</span> <span class="mi">0</span><span class="p">}</span>
|
assert.Equal(t, `<pre class="chroma"><code><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">(</span><span class="nx">aa</span> <span class="kt">string</span><span class="p">)</span> <span class="kt">int</span> <span class="p">{</span><span class="k">return</span> <span class="mi">0</span><span class="p">}</span>
|
||||||
</span></span></code></pre>`, string(b))
|
</span></span></code></pre>`, string(b))
|
||||||
assert.NoError(t, resp.Body.Close())
|
assert.NoError(t, resp.Body.Close())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,23 +60,6 @@ func TestRest_FileServer(t *testing.T) {
|
|||||||
_ = os.Remove(testHTMLFile)
|
_ = os.Remove(testHTMLFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRest_GetStarted(t *testing.T) {
|
|
||||||
ts, _, teardown := startupT(t)
|
|
||||||
defer teardown()
|
|
||||||
|
|
||||||
getStartedHTML := os.TempDir() + "/getstarted.html"
|
|
||||||
err := os.WriteFile(getStartedHTML, []byte("some html blah"), 0o700)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
body, code := get(t, ts.URL+"/index.html")
|
|
||||||
assert.Equal(t, http.StatusOK, code)
|
|
||||||
assert.Equal(t, "some html blah", body)
|
|
||||||
|
|
||||||
_ = os.Remove(getStartedHTML)
|
|
||||||
_, code = get(t, ts.URL+"/index.html")
|
|
||||||
assert.Equal(t, http.StatusNotFound, code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRest_Shutdown(t *testing.T) {
|
func TestRest_Shutdown(t *testing.T) {
|
||||||
srv := Rest{Authenticator: &auth.Service{}, ImageProxy: &proxy.Image{}}
|
srv := Rest{Authenticator: &auth.Service{}, ImageProxy: &proxy.Image{}}
|
||||||
done := make(chan bool)
|
done := make(chan bool)
|
||||||
@@ -389,6 +372,38 @@ func TestRest_subscribersOnly(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_validEmailAuth(t *testing.T) {
|
||||||
|
tbl := []struct {
|
||||||
|
req string
|
||||||
|
status int
|
||||||
|
}{
|
||||||
|
{"/auth/email/login?site=remark42&address=umputun%example.com&user=someone", http.StatusOK},
|
||||||
|
{"/auth/email/login?site=site-with-dash_and_underscore-and.dot&address=umputun%example.com&user=someone", http.StatusOK},
|
||||||
|
{"/auth/email/login?site=remark42&address=umputun%example.com&user=someone+blah", http.StatusOK},
|
||||||
|
{"/auth/email/login?site=remark42&address=umputun%example.com&user=Евгений+Умпутун", http.StatusOK},
|
||||||
|
{"/auth/email/login?site=remark42&address=umputun%example.com&user=12", http.StatusForbidden},
|
||||||
|
{"/auth/email/login?site=remark42&address=umputun%example.com&user=..blah+blah", http.StatusForbidden},
|
||||||
|
{"/auth/email/login?site=remark42&address=umputun%example.com&user=someonelooong+loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong", http.StatusForbidden},
|
||||||
|
{"/auth/twitter/login?site=remark42&address=umputun%example.com&user=..blah+blah", http.StatusOK},
|
||||||
|
{"/auth/email/login?site=remark42&address=umputun%example.com", http.StatusOK},
|
||||||
|
{"/auth/email/login?site=remark42&address=umputun+example.com&user=someone", http.StatusForbidden},
|
||||||
|
{"/auth/email/login?site=bad!site&address=umputun%example.com&user=someone", http.StatusForbidden},
|
||||||
|
{"/auth/email/login?site=loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongsite&address=umputun%example.com&user=someone", http.StatusForbidden},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tbl {
|
||||||
|
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||||
|
req := httptest.NewRequest("GET", "http://example.com"+tt.req, http.NoBody)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
h := validEmailAuth()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||||
|
h.ServeHTTP(w, req)
|
||||||
|
resp := w.Result()
|
||||||
|
assert.Equal(t, tt.status, resp.StatusCode)
|
||||||
|
assert.NoError(t, resp.Body.Close())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// randomPath pick a file or folder name which is not in use for sure
|
// randomPath pick a file or folder name which is not in use for sure
|
||||||
func randomPath(tempDir, basename, suffix string) (string, error) {
|
func randomPath(tempDir, basename, suffix string) (string, error) {
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
|
|||||||
@@ -111,15 +111,11 @@ func (s *Rest) getRemarkHost() string {
|
|||||||
|
|
||||||
func (s *Rest) makeTLSConfig() *tls.Config {
|
func (s *Rest) makeTLSConfig() *tls.Config {
|
||||||
return &tls.Config{
|
return &tls.Config{
|
||||||
PreferServerCipherSuites: true,
|
|
||||||
CipherSuites: []uint16{
|
CipherSuites: []uint16{
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
// tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
|
||||||
// tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
// tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
|
||||||
},
|
},
|
||||||
MinVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS12,
|
||||||
CurvePreferences: []tls.CurveID{
|
CurvePreferences: []tls.CurveID{
|
||||||
|
|||||||
@@ -48,9 +48,9 @@ type errTmplData struct {
|
|||||||
Details string
|
Details string
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendErrorHTML makes html body with provided template and responds with provided http status code,
|
// SendErrorHTML makes html body from error_response.html.tmpl template and responds with provided http status code,
|
||||||
// error code is not included in render as it is intended for UI developers and not for the users
|
// error code is not included in render as it is intended for UI developers and not for the users
|
||||||
func SendErrorHTML(w http.ResponseWriter, r *http.Request, httpStatusCode int, err error, details string, errCode int, t templates.FileReader) {
|
func SendErrorHTML(w http.ResponseWriter, r *http.Request, httpStatusCode int, err error, details string, errCode int) {
|
||||||
// MustExecute behaves like template.Execute, but panics if an error occurs.
|
// MustExecute behaves like template.Execute, but panics if an error occurs.
|
||||||
MustExecute := func(tmpl *template.Template, wr io.Writer, data interface{}) {
|
MustExecute := func(tmpl *template.Template, wr io.Writer, data interface{}) {
|
||||||
if err = tmpl.Execute(wr, data); err != nil {
|
if err = tmpl.Execute(wr, data); err != nil {
|
||||||
@@ -58,7 +58,7 @@ func SendErrorHTML(w http.ResponseWriter, r *http.Request, httpStatusCode int, e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
MustRead := func(path string) string {
|
MustRead := func(path string) string {
|
||||||
file, e := t.ReadFile(path)
|
file, e := templates.Read(path)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
panic(e)
|
panic(e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,18 +36,11 @@ func TestSendErrorJSON(t *testing.T) {
|
|||||||
assert.Equal(t, `{"code":123,"details":"error details 123456","error":"error 500"}`+"\n", string(body))
|
assert.Equal(t, `{"code":123,"details":"error details 123456","error":"error 500"}`+"\n", string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
type MockFS struct{}
|
|
||||||
|
|
||||||
func (fs *MockFS) ReadFile(path string) ([]byte, error) {
|
|
||||||
return []byte(fmt.Sprintf("{{.Error}}{{.Details}} %s", path)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSendErrorHTML(t *testing.T) {
|
func TestSendErrorHTML(t *testing.T) {
|
||||||
fs := &MockFS{}
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path == "/error" {
|
if r.URL.Path == "/error" {
|
||||||
t.Log("http err request", r.URL)
|
t.Log("http err request", r.URL)
|
||||||
SendErrorHTML(w, r, 500, fmt.Errorf("error 500"), "error details 123456", 987, fs)
|
SendErrorHTML(w, r, 500, fmt.Errorf("error 500"), "error details 123456", 987)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.WriteHeader(404)
|
w.WriteHeader(404)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/umputun/remark42/backend/app/store/image"
|
"github.com/umputun/remark42/backend/app/store/image"
|
||||||
@@ -95,7 +94,8 @@ func TestImage_Replace(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestImage_Routes(t *testing.T) {
|
func TestImage_Routes(t *testing.T) {
|
||||||
imageStore := image.MockStore{}
|
// no image supposed to be cached
|
||||||
|
imageStore := image.StoreMock{LoadFunc: func(id string) ([]byte, error) { return nil, nil }}
|
||||||
img := Image{
|
img := Image{
|
||||||
HTTP2HTTPS: true,
|
HTTP2HTTPS: true,
|
||||||
RemarkURL: "https://demo.remark42.com",
|
RemarkURL: "https://demo.remark42.com",
|
||||||
@@ -110,9 +110,6 @@ func TestImage_Routes(t *testing.T) {
|
|||||||
|
|
||||||
encodedImgURL := base64.URLEncoding.EncodeToString([]byte(httpSrv.URL + "/image/img1.png"))
|
encodedImgURL := base64.URLEncoding.EncodeToString([]byte(httpSrv.URL + "/image/img1.png"))
|
||||||
|
|
||||||
// no image supposed to be cached
|
|
||||||
imageStore.On("Load", mock.Anything).Times(2).Return(nil, nil)
|
|
||||||
|
|
||||||
resp, err := http.Get(ts.URL + "/?src=" + encodedImgURL)
|
resp, err := http.Get(ts.URL + "/?src=" + encodedImgURL)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NoError(t, resp.Body.Close())
|
assert.NoError(t, resp.Body.Close())
|
||||||
@@ -131,10 +128,11 @@ func TestImage_Routes(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NoError(t, resp.Body.Close())
|
assert.NoError(t, resp.Body.Close())
|
||||||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||||
|
assert.Equal(t, 2, len(imageStore.LoadCalls()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImage_DisabledCachingAndHTTP2HTTPS(t *testing.T) {
|
func TestImage_DisabledCachingAndHTTP2HTTPS(t *testing.T) {
|
||||||
imageStore := image.MockStore{}
|
imageStore := image.StoreMock{LoadFunc: func(id string) ([]byte, error) { return nil, nil }}
|
||||||
img := Image{
|
img := Image{
|
||||||
RemarkURL: "https://demo.remark42.com",
|
RemarkURL: "https://demo.remark42.com",
|
||||||
RoutePath: "/api/v1/proxy",
|
RoutePath: "/api/v1/proxy",
|
||||||
@@ -148,8 +146,6 @@ func TestImage_DisabledCachingAndHTTP2HTTPS(t *testing.T) {
|
|||||||
|
|
||||||
encodedImgURL := base64.URLEncoding.EncodeToString([]byte(httpSrv.URL + "/image/img1.png"))
|
encodedImgURL := base64.URLEncoding.EncodeToString([]byte(httpSrv.URL + "/image/img1.png"))
|
||||||
|
|
||||||
imageStore.On("Load", mock.Anything).Once().Return(nil, nil)
|
|
||||||
|
|
||||||
resp, err := http.Get(ts.URL + "/?src=" + encodedImgURL)
|
resp, err := http.Get(ts.URL + "/?src=" + encodedImgURL)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NoError(t, resp.Body.Close())
|
assert.NoError(t, resp.Body.Close())
|
||||||
@@ -157,11 +153,18 @@ func TestImage_DisabledCachingAndHTTP2HTTPS(t *testing.T) {
|
|||||||
assert.Equal(t, "1462", resp.Header["Content-Length"][0])
|
assert.Equal(t, "1462", resp.Header["Content-Length"][0])
|
||||||
assert.Equal(t, "image/png", resp.Header["Content-Type"][0])
|
assert.Equal(t, "image/png", resp.Header["Content-Type"][0])
|
||||||
|
|
||||||
imageStore.AssertCalled(t, "Load", mock.Anything)
|
assert.Equal(t, 1, len(imageStore.LoadCalls()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImage_RoutesCachingImage(t *testing.T) {
|
func TestImage_RoutesCachingImage(t *testing.T) {
|
||||||
imageStore := image.MockStore{}
|
imageStore := image.StoreMock{
|
||||||
|
LoadFunc: func(id string) ([]byte, error) {
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
SaveFunc: func(id string, img []byte) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
img := Image{
|
img := Image{
|
||||||
CacheExternal: true,
|
CacheExternal: true,
|
||||||
RemarkURL: "https://demo.remark42.com",
|
RemarkURL: "https://demo.remark42.com",
|
||||||
@@ -177,9 +180,6 @@ func TestImage_RoutesCachingImage(t *testing.T) {
|
|||||||
imgURL := httpSrv.URL + "/image/img1.png"
|
imgURL := httpSrv.URL + "/image/img1.png"
|
||||||
encodedImgURL := base64.URLEncoding.EncodeToString([]byte(imgURL))
|
encodedImgURL := base64.URLEncoding.EncodeToString([]byte(imgURL))
|
||||||
|
|
||||||
imageStore.On("Load", mock.Anything).Once().Return(nil, nil)
|
|
||||||
imageStore.On("Save", mock.Anything, mock.Anything).Once().Return(nil)
|
|
||||||
|
|
||||||
resp, err := http.Get(ts.URL + "/?src=" + encodedImgURL)
|
resp, err := http.Get(ts.URL + "/?src=" + encodedImgURL)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NoError(t, resp.Body.Close())
|
assert.NoError(t, resp.Body.Close())
|
||||||
@@ -187,12 +187,18 @@ func TestImage_RoutesCachingImage(t *testing.T) {
|
|||||||
assert.Equal(t, "1462", resp.Header["Content-Length"][0])
|
assert.Equal(t, "1462", resp.Header["Content-Length"][0])
|
||||||
assert.Equal(t, "image/png", resp.Header["Content-Type"][0])
|
assert.Equal(t, "image/png", resp.Header["Content-Type"][0])
|
||||||
|
|
||||||
imageStore.AssertCalled(t, "Load", mock.Anything)
|
assert.Equal(t, 1, len(imageStore.LoadCalls()))
|
||||||
imageStore.AssertCalled(t, "Save", "cached_images/4b84b15bff6ee5796152495a230e45e3d7e947d9-"+image.Sha1Str(imgURL), gopherPNGBytes())
|
assert.Equal(t, 1, len(imageStore.SaveCalls()))
|
||||||
|
assert.Equal(t, "cached_images/4b84b15bff6ee5796152495a230e45e3d7e947d9-"+image.Sha1Str(imgURL), imageStore.SaveCalls()[0].ID)
|
||||||
|
assert.Equal(t, gopherPNGBytes(), imageStore.SaveCalls()[0].Img)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImage_RoutesUsingCachedImage(t *testing.T) {
|
func TestImage_RoutesUsingCachedImage(t *testing.T) {
|
||||||
imageStore := image.MockStore{}
|
// In order to validate that cached data used cache "will return" some other data from what http server would
|
||||||
|
testImage := []byte(fmt.Sprintf("%256s", "X"))
|
||||||
|
imageStore := image.StoreMock{LoadFunc: func(id string) ([]byte, error) {
|
||||||
|
return testImage, nil
|
||||||
|
}}
|
||||||
img := Image{
|
img := Image{
|
||||||
CacheExternal: true,
|
CacheExternal: true,
|
||||||
RemarkURL: "https://demo.remark42.com",
|
RemarkURL: "https://demo.remark42.com",
|
||||||
@@ -207,10 +213,6 @@ func TestImage_RoutesUsingCachedImage(t *testing.T) {
|
|||||||
|
|
||||||
encodedImgURL := base64.URLEncoding.EncodeToString([]byte(httpSrv.URL + "/image/img1.png"))
|
encodedImgURL := base64.URLEncoding.EncodeToString([]byte(httpSrv.URL + "/image/img1.png"))
|
||||||
|
|
||||||
// In order to validate that cached data used cache "will return" some other data from what http server would
|
|
||||||
testImage := []byte(fmt.Sprintf("%256s", "X"))
|
|
||||||
imageStore.On("Load", mock.Anything).Once().Return(testImage, nil)
|
|
||||||
|
|
||||||
resp, err := http.Get(ts.URL + "/?src=" + encodedImgURL)
|
resp, err := http.Get(ts.URL + "/?src=" + encodedImgURL)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NoError(t, resp.Body.Close())
|
assert.NoError(t, resp.Body.Close())
|
||||||
@@ -219,11 +221,12 @@ func TestImage_RoutesUsingCachedImage(t *testing.T) {
|
|||||||
assert.Equal(t, "text/plain; charset=utf-8", resp.Header["Content-Type"][0],
|
assert.Equal(t, "text/plain; charset=utf-8", resp.Header["Content-Type"][0],
|
||||||
"if you save text you receive text/plain in response, that's only fair option you got")
|
"if you save text you receive text/plain in response, that's only fair option you got")
|
||||||
|
|
||||||
imageStore.AssertCalled(t, "Load", mock.Anything)
|
assert.Equal(t, 1, len(imageStore.LoadCalls()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImage_RoutesTimedOut(t *testing.T) {
|
func TestImage_RoutesTimedOut(t *testing.T) {
|
||||||
imageStore := image.MockStore{}
|
// no image supposed to be cached
|
||||||
|
imageStore := image.StoreMock{LoadFunc: func(id string) ([]byte, error) { return nil, nil }}
|
||||||
img := Image{
|
img := Image{
|
||||||
HTTP2HTTPS: true,
|
HTTP2HTTPS: true,
|
||||||
RemarkURL: "https://demo.remark42.com",
|
RemarkURL: "https://demo.remark42.com",
|
||||||
@@ -239,9 +242,6 @@ func TestImage_RoutesTimedOut(t *testing.T) {
|
|||||||
|
|
||||||
encodedImgURL := base64.URLEncoding.EncodeToString([]byte(httpSrv.URL + "/image/img-slow.png"))
|
encodedImgURL := base64.URLEncoding.EncodeToString([]byte(httpSrv.URL + "/image/img-slow.png"))
|
||||||
|
|
||||||
// no image supposed to be cached
|
|
||||||
imageStore.On("Load", mock.Anything).Once().Return(nil, nil)
|
|
||||||
|
|
||||||
resp, err := http.Get(ts.URL + "/?src=" + encodedImgURL)
|
resp, err := http.Get(ts.URL + "/?src=" + encodedImgURL)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
|
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
|
||||||
@@ -250,6 +250,7 @@ func TestImage_RoutesTimedOut(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Log(string(b))
|
t.Log(string(b))
|
||||||
assert.True(t, strings.Contains(string(b), "deadline exceeded"))
|
assert.True(t, strings.Contains(string(b), "deadline exceeded"))
|
||||||
|
assert.Equal(t, 1, len(imageStore.LoadCalls()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImage_ConvertProxyMode(t *testing.T) {
|
func TestImage_ConvertProxyMode(t *testing.T) {
|
||||||
|
|||||||
@@ -78,5 +78,5 @@ func (s *StaticStore) Enabled(site string) (ok bool, err error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnEvent doesn nothing for StaticStore
|
// OnEvent does nothing for StaticStore
|
||||||
func (s *StaticStore) OnEvent(_ string, _ EventType) error { return nil }
|
func (s *StaticStore) OnEvent(_ string, _ EventType) error { return nil }
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type Comment struct {
|
|||||||
ID string `json:"id" bson:"_id"`
|
ID string `json:"id" bson:"_id"`
|
||||||
ParentID string `json:"pid"`
|
ParentID string `json:"pid"`
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
Orig string `json:"orig,omitempty"`
|
Orig string `json:"orig,omitempty"` // important: never render this as HTML! It's not sanitized.
|
||||||
User User `json:"user"`
|
User User `json:"user"`
|
||||||
Locator Locator `json:"locator"`
|
Locator Locator `json:"locator"`
|
||||||
Score int `json:"score"`
|
Score int `json:"score"`
|
||||||
@@ -112,20 +112,21 @@ func (c *Comment) SetDeleted(mode DeleteMode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanitize clean dangerous html/js from the comment
|
// Sanitize clean dangerous html/js from the comment.
|
||||||
|
// Comment.Orig which is used to store the original comment text is not sanitized
|
||||||
|
// as we expect to never render it as HTML and render Comment.Text instead
|
||||||
func (c *Comment) Sanitize() {
|
func (c *Comment) Sanitize() {
|
||||||
p := bluemonday.UGCPolicy()
|
p := bluemonday.UGCPolicy()
|
||||||
p.AllowAttrs("class").Matching(regexp.MustCompile("^chroma$")).OnElements("pre")
|
p.AllowAttrs("class").Matching(regexp.MustCompile("^chroma$")).OnElements("pre")
|
||||||
// this is list of <span> tag classes which could be produced by chroma code renderer
|
// this is list of <span> tag classes which could be produced by chroma code renderer
|
||||||
// source: https://github.com/alecthomas/chroma/blob/022b6f4fc2c4aa819aac18363c8de3f70619200b/types.go#L221-L316
|
// source: https://github.com/alecthomas/chroma/blob/cc2dd5b/types.go#L211-L307
|
||||||
const codeSpanClassRegex = "^(chroma|ln|lnt|hl|lntable|lntd|w|err|x|esc|k|kc" +
|
const codeSpanClassRegex = "^(bg|chroma|line|ln|lnt|hl|lntable|lntd|cl|w|err|x|k|kc" +
|
||||||
"|kd|kn|kp|kr|kt|n|na|nb|bp|nc|no|nd|ni|ne|nf|fm|py|nl|nn|nx|nt|nv|vc|vg" +
|
"|kd|kn|kp|kr|kt|n|na|nb|bp|nc|no|nd|ni|ne|nf|fm|py|nl|nn|nx|nt|nv|vc|vg" +
|
||||||
"|vi|vm|l|ld|s|sa|sb|sc|dl|sd|s2|se|sh|si|sx|sr|s1|ss|m|mb|mf|mh|mi|il" +
|
"|vi|vm|l|ld|s|sa|sb|sc|dl|sd|s2|se|sh|si|sx|sr|s1|ss|m|mb|mf|mh|mi|il" +
|
||||||
"|mo|o|ow|p|c|ch|cm|cp|cpf|c1|cs|g|gd|ge|gr|gh|gi|go|gp|gs|gu|gt|gl)$"
|
"|mo|o|ow|p|c|ch|cm|cp|cpf|c1|cs|g|gd|ge|gr|gh|gi|go|gp|gs|gu|gt|gl)$"
|
||||||
p.AllowAttrs("class").Matching(regexp.MustCompile(codeSpanClassRegex)).OnElements("span")
|
p.AllowAttrs("class").Matching(regexp.MustCompile(codeSpanClassRegex)).OnElements("span")
|
||||||
p.AllowAttrs("loading").Matching(regexp.MustCompile("^(lazy|eager)$")).OnElements("img")
|
p.AllowAttrs("loading").Matching(regexp.MustCompile("^(lazy|eager)$")).OnElements("img")
|
||||||
c.Text = p.Sanitize(c.Text)
|
c.Text = p.Sanitize(c.Text)
|
||||||
c.Orig = p.Sanitize(c.Orig)
|
|
||||||
c.User.ID = template.HTMLEscapeString(c.User.ID)
|
c.User.ID = template.HTMLEscapeString(c.User.ID)
|
||||||
c.User.Name = c.SanitizeText(c.User.Name)
|
c.User.Name = c.SanitizeText(c.User.Name)
|
||||||
c.User.Picture = c.SanitizeAsURL(c.User.Picture)
|
c.User.Picture = c.SanitizeAsURL(c.User.Picture)
|
||||||
|
|||||||
@@ -16,15 +16,15 @@ import (
|
|||||||
|
|
||||||
// BoltDB implements store.Interface, represents multiple sites with multiplexing to different bolt dbs. Thread safe.
|
// BoltDB implements store.Interface, represents multiple sites with multiplexing to different bolt dbs. Thread safe.
|
||||||
// there are 6 types of top-level buckets:
|
// there are 6 types of top-level buckets:
|
||||||
// - comments for post in "posts" top-level bucket. Each url (post) makes its own bucket and each k:v pair is commentID:comment
|
// - comments for post in "posts" top-level bucket. Each url (post) makes its own bucket and each k:v pair is commentID:comment
|
||||||
// - history of all comments. They all in a single "last" bucket (per site) and key is defined by ref struct as ts+commentID
|
// - history of all comments. They all in a single "last" bucket (per site) and key is defined by ref struct as ts+commentID
|
||||||
// value is not full comment but a reference combined from post-url+commentID
|
// value is not full comment but a reference combined from post-url+commentID
|
||||||
// - user to comment references in "users" bucket. It used to get comments for user. Key is userID and value
|
// - user to comment references in "users" bucket. It used to get comments for user. Key is userID and value
|
||||||
// is a nested bucket named userID with kv as ts:reference
|
// is a nested bucket named userID with kv as ts:reference
|
||||||
// - users details in "user_details" bucket. Key is userID, value - UserDetailEntry
|
// - users details in "user_details" bucket. Key is userID, value - UserDetailEntry
|
||||||
// - blocking info sits in "block" bucket. Key is userID, value - ts
|
// - blocking info sits in "block" bucket. Key is userID, value - ts
|
||||||
// - counts per post to keep number of comments. Key is post url, value - count
|
// - counts per post to keep number of comments. Key is post url, value - count
|
||||||
// - readonly per post to keep status of manually set RO posts. Key is post url, value - ts
|
// - readonly per post to keep status of manually set RO posts. Key is post url, value - ts
|
||||||
type BoltDB struct {
|
type BoltDB struct {
|
||||||
dbs map[string]*bolt.DB
|
dbs map[string]*bolt.DB
|
||||||
}
|
}
|
||||||
@@ -780,7 +780,7 @@ func (b *BoltDB) deleteUserDetail(bdb *bolt.DB, userID string, userDetail UserDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
return bdb.Update(func(tx *bolt.Tx) error {
|
return bdb.Update(func(tx *bolt.Tx) error {
|
||||||
// updated entry is not empty and we need to store it's updated copy
|
// updated entry is not empty and we need to store its updated copy
|
||||||
err := b.save(tx.Bucket([]byte(userDetailsBucketName)), userID, entry)
|
err := b.save(tx.Bucket([]byte(userDetailsBucketName)), userID, entry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update detail %s for %s: %w", userDetail, userID, err)
|
return fmt.Errorf("failed to update detail %s for %s: %w", userDetail, userID, err)
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import (
|
|||||||
"github.com/umputun/remark42/backend/app/store"
|
"github.com/umputun/remark42/backend/app/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NOTE: mockery works from linked to go-path and with GOFLAGS='-mod=vendor' go generate
|
// NOTE: matryer/moq should be installed globally and works with `go generate ./...`
|
||||||
//go:generate sh -c "mockery -inpkg -name Interface -print > /tmp/engine-mock.tmp && mv /tmp/engine-mock.tmp engine_mock.go"
|
//go:generate moq --out engine_mock.go . Interface
|
||||||
|
|
||||||
// Interface defines methods provided by low-level storage engine
|
// Interface defines methods provided by low-level storage engine
|
||||||
type Interface interface {
|
type Interface interface {
|
||||||
|
|||||||
@@ -1,231 +1,508 @@
|
|||||||
// Code generated by mockery v1.1.2. DO NOT EDIT.
|
// Code generated by moq; DO NOT EDIT.
|
||||||
|
// github.com/matryer/moq
|
||||||
|
|
||||||
package engine
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
mock "github.com/stretchr/testify/mock"
|
|
||||||
store "github.com/umputun/remark42/backend/app/store"
|
store "github.com/umputun/remark42/backend/app/store"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockInterface is an autogenerated mock type for the Interface type
|
// Ensure, that InterfaceMock does implement Interface.
|
||||||
type MockInterface struct {
|
// If this is not the case, regenerate this file with moq.
|
||||||
mock.Mock
|
var _ Interface = &InterfaceMock{}
|
||||||
}
|
|
||||||
|
|
||||||
// Close provides a mock function with given fields:
|
// InterfaceMock is a mock implementation of Interface.
|
||||||
func (_m *MockInterface) Close() error {
|
//
|
||||||
ret := _m.Called()
|
// func TestSomethingThatUsesInterface(t *testing.T) {
|
||||||
|
//
|
||||||
|
// // make and configure a mocked Interface
|
||||||
|
// mockedInterface := &InterfaceMock{
|
||||||
|
// CloseFunc: func() error {
|
||||||
|
// panic("mock out the Close method")
|
||||||
|
// },
|
||||||
|
// CountFunc: func(req FindRequest) (int, error) {
|
||||||
|
// panic("mock out the Count method")
|
||||||
|
// },
|
||||||
|
// CreateFunc: func(comment store.Comment) (string, error) {
|
||||||
|
// panic("mock out the Create method")
|
||||||
|
// },
|
||||||
|
// DeleteFunc: func(req DeleteRequest) error {
|
||||||
|
// panic("mock out the Delete method")
|
||||||
|
// },
|
||||||
|
// FindFunc: func(req FindRequest) ([]store.Comment, error) {
|
||||||
|
// panic("mock out the Find method")
|
||||||
|
// },
|
||||||
|
// FlagFunc: func(req FlagRequest) (bool, error) {
|
||||||
|
// panic("mock out the Flag method")
|
||||||
|
// },
|
||||||
|
// GetFunc: func(req GetRequest) (store.Comment, error) {
|
||||||
|
// panic("mock out the Get method")
|
||||||
|
// },
|
||||||
|
// InfoFunc: func(req InfoRequest) ([]store.PostInfo, error) {
|
||||||
|
// panic("mock out the Info method")
|
||||||
|
// },
|
||||||
|
// ListFlagsFunc: func(req FlagRequest) ([]interface{}, error) {
|
||||||
|
// panic("mock out the ListFlags method")
|
||||||
|
// },
|
||||||
|
// UpdateFunc: func(comment store.Comment) error {
|
||||||
|
// panic("mock out the Update method")
|
||||||
|
// },
|
||||||
|
// UserDetailFunc: func(req UserDetailRequest) ([]UserDetailEntry, error) {
|
||||||
|
// panic("mock out the UserDetail method")
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // use mockedInterface in code that requires Interface
|
||||||
|
// // and then make assertions.
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
type InterfaceMock struct {
|
||||||
|
// CloseFunc mocks the Close method.
|
||||||
|
CloseFunc func() error
|
||||||
|
|
||||||
var r0 error
|
// CountFunc mocks the Count method.
|
||||||
if rf, ok := ret.Get(0).(func() error); ok {
|
CountFunc func(req FindRequest) (int, error)
|
||||||
r0 = rf()
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
// CreateFunc mocks the Create method.
|
||||||
}
|
CreateFunc func(comment store.Comment) (string, error)
|
||||||
|
|
||||||
// Count provides a mock function with given fields: req
|
// DeleteFunc mocks the Delete method.
|
||||||
func (_m *MockInterface) Count(req FindRequest) (int, error) {
|
DeleteFunc func(req DeleteRequest) error
|
||||||
ret := _m.Called(req)
|
|
||||||
|
|
||||||
var r0 int
|
// FindFunc mocks the Find method.
|
||||||
if rf, ok := ret.Get(0).(func(FindRequest) int); ok {
|
FindFunc func(req FindRequest) ([]store.Comment, error)
|
||||||
r0 = rf(req)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Get(0).(int)
|
|
||||||
}
|
|
||||||
|
|
||||||
var r1 error
|
// FlagFunc mocks the Flag method.
|
||||||
if rf, ok := ret.Get(1).(func(FindRequest) error); ok {
|
FlagFunc func(req FlagRequest) (bool, error)
|
||||||
r1 = rf(req)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
// GetFunc mocks the Get method.
|
||||||
}
|
GetFunc func(req GetRequest) (store.Comment, error)
|
||||||
|
|
||||||
// Create provides a mock function with given fields: comment
|
// InfoFunc mocks the Info method.
|
||||||
func (_m *MockInterface) Create(comment store.Comment) (string, error) {
|
InfoFunc func(req InfoRequest) ([]store.PostInfo, error)
|
||||||
ret := _m.Called(comment)
|
|
||||||
|
|
||||||
var r0 string
|
// ListFlagsFunc mocks the ListFlags method.
|
||||||
if rf, ok := ret.Get(0).(func(store.Comment) string); ok {
|
ListFlagsFunc func(req FlagRequest) ([]interface{}, error)
|
||||||
r0 = rf(comment)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Get(0).(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
var r1 error
|
// UpdateFunc mocks the Update method.
|
||||||
if rf, ok := ret.Get(1).(func(store.Comment) error); ok {
|
UpdateFunc func(comment store.Comment) error
|
||||||
r1 = rf(comment)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
// UserDetailFunc mocks the UserDetail method.
|
||||||
}
|
UserDetailFunc func(req UserDetailRequest) ([]UserDetailEntry, error)
|
||||||
|
|
||||||
// Delete provides a mock function with given fields: req
|
// calls tracks calls to the methods.
|
||||||
func (_m *MockInterface) Delete(req DeleteRequest) error {
|
calls struct {
|
||||||
ret := _m.Called(req)
|
// Close holds details about calls to the Close method.
|
||||||
|
Close []struct {
|
||||||
var r0 error
|
}
|
||||||
if rf, ok := ret.Get(0).(func(DeleteRequest) error); ok {
|
// Count holds details about calls to the Count method.
|
||||||
r0 = rf(req)
|
Count []struct {
|
||||||
} else {
|
// Req is the req argument value.
|
||||||
r0 = ret.Error(0)
|
Req FindRequest
|
||||||
}
|
}
|
||||||
|
// Create holds details about calls to the Create method.
|
||||||
return r0
|
Create []struct {
|
||||||
}
|
// Comment is the comment argument value.
|
||||||
|
Comment store.Comment
|
||||||
// Find provides a mock function with given fields: req
|
}
|
||||||
func (_m *MockInterface) Find(req FindRequest) ([]store.Comment, error) {
|
// Delete holds details about calls to the Delete method.
|
||||||
ret := _m.Called(req)
|
Delete []struct {
|
||||||
|
// Req is the req argument value.
|
||||||
var r0 []store.Comment
|
Req DeleteRequest
|
||||||
if rf, ok := ret.Get(0).(func(FindRequest) []store.Comment); ok {
|
}
|
||||||
r0 = rf(req)
|
// Find holds details about calls to the Find method.
|
||||||
} else {
|
Find []struct {
|
||||||
if ret.Get(0) != nil {
|
// Req is the req argument value.
|
||||||
r0 = ret.Get(0).([]store.Comment)
|
Req FindRequest
|
||||||
|
}
|
||||||
|
// Flag holds details about calls to the Flag method.
|
||||||
|
Flag []struct {
|
||||||
|
// Req is the req argument value.
|
||||||
|
Req FlagRequest
|
||||||
|
}
|
||||||
|
// Get holds details about calls to the Get method.
|
||||||
|
Get []struct {
|
||||||
|
// Req is the req argument value.
|
||||||
|
Req GetRequest
|
||||||
|
}
|
||||||
|
// Info holds details about calls to the Info method.
|
||||||
|
Info []struct {
|
||||||
|
// Req is the req argument value.
|
||||||
|
Req InfoRequest
|
||||||
|
}
|
||||||
|
// ListFlags holds details about calls to the ListFlags method.
|
||||||
|
ListFlags []struct {
|
||||||
|
// Req is the req argument value.
|
||||||
|
Req FlagRequest
|
||||||
|
}
|
||||||
|
// Update holds details about calls to the Update method.
|
||||||
|
Update []struct {
|
||||||
|
// Comment is the comment argument value.
|
||||||
|
Comment store.Comment
|
||||||
|
}
|
||||||
|
// UserDetail holds details about calls to the UserDetail method.
|
||||||
|
UserDetail []struct {
|
||||||
|
// Req is the req argument value.
|
||||||
|
Req UserDetailRequest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
lockClose sync.RWMutex
|
||||||
var r1 error
|
lockCount sync.RWMutex
|
||||||
if rf, ok := ret.Get(1).(func(FindRequest) error); ok {
|
lockCreate sync.RWMutex
|
||||||
r1 = rf(req)
|
lockDelete sync.RWMutex
|
||||||
} else {
|
lockFind sync.RWMutex
|
||||||
r1 = ret.Error(1)
|
lockFlag sync.RWMutex
|
||||||
}
|
lockGet sync.RWMutex
|
||||||
|
lockInfo sync.RWMutex
|
||||||
return r0, r1
|
lockListFlags sync.RWMutex
|
||||||
|
lockUpdate sync.RWMutex
|
||||||
|
lockUserDetail sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flag provides a mock function with given fields: req
|
// Close calls CloseFunc.
|
||||||
func (_m *MockInterface) Flag(req FlagRequest) (bool, error) {
|
func (mock *InterfaceMock) Close() error {
|
||||||
ret := _m.Called(req)
|
if mock.CloseFunc == nil {
|
||||||
|
panic("InterfaceMock.CloseFunc: method is nil but Interface.Close was just called")
|
||||||
var r0 bool
|
|
||||||
if rf, ok := ret.Get(0).(func(FlagRequest) bool); ok {
|
|
||||||
r0 = rf(req)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Get(0).(bool)
|
|
||||||
}
|
}
|
||||||
|
callInfo := struct {
|
||||||
var r1 error
|
}{}
|
||||||
if rf, ok := ret.Get(1).(func(FlagRequest) error); ok {
|
mock.lockClose.Lock()
|
||||||
r1 = rf(req)
|
mock.calls.Close = append(mock.calls.Close, callInfo)
|
||||||
} else {
|
mock.lockClose.Unlock()
|
||||||
r1 = ret.Error(1)
|
return mock.CloseFunc()
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get provides a mock function with given fields: req
|
// CloseCalls gets all the calls that were made to Close.
|
||||||
func (_m *MockInterface) Get(req GetRequest) (store.Comment, error) {
|
// Check the length with:
|
||||||
ret := _m.Called(req)
|
//
|
||||||
|
// len(mockedInterface.CloseCalls())
|
||||||
var r0 store.Comment
|
func (mock *InterfaceMock) CloseCalls() []struct {
|
||||||
if rf, ok := ret.Get(0).(func(GetRequest) store.Comment); ok {
|
} {
|
||||||
r0 = rf(req)
|
var calls []struct {
|
||||||
} else {
|
|
||||||
r0 = ret.Get(0).(store.Comment)
|
|
||||||
}
|
}
|
||||||
|
mock.lockClose.RLock()
|
||||||
var r1 error
|
calls = mock.calls.Close
|
||||||
if rf, ok := ret.Get(1).(func(GetRequest) error); ok {
|
mock.lockClose.RUnlock()
|
||||||
r1 = rf(req)
|
return calls
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info provides a mock function with given fields: req
|
// Count calls CountFunc.
|
||||||
func (_m *MockInterface) Info(req InfoRequest) ([]store.PostInfo, error) {
|
func (mock *InterfaceMock) Count(req FindRequest) (int, error) {
|
||||||
ret := _m.Called(req)
|
if mock.CountFunc == nil {
|
||||||
|
panic("InterfaceMock.CountFunc: method is nil but Interface.Count was just called")
|
||||||
var r0 []store.PostInfo
|
|
||||||
if rf, ok := ret.Get(0).(func(InfoRequest) []store.PostInfo); ok {
|
|
||||||
r0 = rf(req)
|
|
||||||
} else {
|
|
||||||
if ret.Get(0) != nil {
|
|
||||||
r0 = ret.Get(0).([]store.PostInfo)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
callInfo := struct {
|
||||||
var r1 error
|
Req FindRequest
|
||||||
if rf, ok := ret.Get(1).(func(InfoRequest) error); ok {
|
}{
|
||||||
r1 = rf(req)
|
Req: req,
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
}
|
||||||
|
mock.lockCount.Lock()
|
||||||
return r0, r1
|
mock.calls.Count = append(mock.calls.Count, callInfo)
|
||||||
|
mock.lockCount.Unlock()
|
||||||
|
return mock.CountFunc(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListFlags provides a mock function with given fields: req
|
// CountCalls gets all the calls that were made to Count.
|
||||||
func (_m *MockInterface) ListFlags(req FlagRequest) ([]interface{}, error) {
|
// Check the length with:
|
||||||
ret := _m.Called(req)
|
//
|
||||||
|
// len(mockedInterface.CountCalls())
|
||||||
var r0 []interface{}
|
func (mock *InterfaceMock) CountCalls() []struct {
|
||||||
if rf, ok := ret.Get(0).(func(FlagRequest) []interface{}); ok {
|
Req FindRequest
|
||||||
r0 = rf(req)
|
} {
|
||||||
} else {
|
var calls []struct {
|
||||||
if ret.Get(0) != nil {
|
Req FindRequest
|
||||||
r0 = ret.Get(0).([]interface{})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
mock.lockCount.RLock()
|
||||||
var r1 error
|
calls = mock.calls.Count
|
||||||
if rf, ok := ret.Get(1).(func(FlagRequest) error); ok {
|
mock.lockCount.RUnlock()
|
||||||
r1 = rf(req)
|
return calls
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update provides a mock function with given fields: comment
|
// Create calls CreateFunc.
|
||||||
func (_m *MockInterface) Update(comment store.Comment) error {
|
func (mock *InterfaceMock) Create(comment store.Comment) (string, error) {
|
||||||
ret := _m.Called(comment)
|
if mock.CreateFunc == nil {
|
||||||
|
panic("InterfaceMock.CreateFunc: method is nil but Interface.Create was just called")
|
||||||
var r0 error
|
|
||||||
if rf, ok := ret.Get(0).(func(store.Comment) error); ok {
|
|
||||||
r0 = rf(comment)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
}
|
||||||
|
callInfo := struct {
|
||||||
return r0
|
Comment store.Comment
|
||||||
|
}{
|
||||||
|
Comment: comment,
|
||||||
|
}
|
||||||
|
mock.lockCreate.Lock()
|
||||||
|
mock.calls.Create = append(mock.calls.Create, callInfo)
|
||||||
|
mock.lockCreate.Unlock()
|
||||||
|
return mock.CreateFunc(comment)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserDetail provides a mock function with given fields: req
|
// CreateCalls gets all the calls that were made to Create.
|
||||||
func (_m *MockInterface) UserDetail(req UserDetailRequest) ([]UserDetailEntry, error) {
|
// Check the length with:
|
||||||
ret := _m.Called(req)
|
//
|
||||||
|
// len(mockedInterface.CreateCalls())
|
||||||
var r0 []UserDetailEntry
|
func (mock *InterfaceMock) CreateCalls() []struct {
|
||||||
if rf, ok := ret.Get(0).(func(UserDetailRequest) []UserDetailEntry); ok {
|
Comment store.Comment
|
||||||
r0 = rf(req)
|
} {
|
||||||
} else {
|
var calls []struct {
|
||||||
if ret.Get(0) != nil {
|
Comment store.Comment
|
||||||
r0 = ret.Get(0).([]UserDetailEntry)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
mock.lockCreate.RLock()
|
||||||
|
calls = mock.calls.Create
|
||||||
|
mock.lockCreate.RUnlock()
|
||||||
|
return calls
|
||||||
|
}
|
||||||
|
|
||||||
var r1 error
|
// Delete calls DeleteFunc.
|
||||||
if rf, ok := ret.Get(1).(func(UserDetailRequest) error); ok {
|
func (mock *InterfaceMock) Delete(req DeleteRequest) error {
|
||||||
r1 = rf(req)
|
if mock.DeleteFunc == nil {
|
||||||
} else {
|
panic("InterfaceMock.DeleteFunc: method is nil but Interface.Delete was just called")
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
}
|
||||||
|
callInfo := struct {
|
||||||
|
Req DeleteRequest
|
||||||
|
}{
|
||||||
|
Req: req,
|
||||||
|
}
|
||||||
|
mock.lockDelete.Lock()
|
||||||
|
mock.calls.Delete = append(mock.calls.Delete, callInfo)
|
||||||
|
mock.lockDelete.Unlock()
|
||||||
|
return mock.DeleteFunc(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteCalls gets all the calls that were made to Delete.
|
||||||
|
// Check the length with:
|
||||||
|
//
|
||||||
|
// len(mockedInterface.DeleteCalls())
|
||||||
|
func (mock *InterfaceMock) DeleteCalls() []struct {
|
||||||
|
Req DeleteRequest
|
||||||
|
} {
|
||||||
|
var calls []struct {
|
||||||
|
Req DeleteRequest
|
||||||
|
}
|
||||||
|
mock.lockDelete.RLock()
|
||||||
|
calls = mock.calls.Delete
|
||||||
|
mock.lockDelete.RUnlock()
|
||||||
|
return calls
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find calls FindFunc.
|
||||||
|
func (mock *InterfaceMock) Find(req FindRequest) ([]store.Comment, error) {
|
||||||
|
if mock.FindFunc == nil {
|
||||||
|
panic("InterfaceMock.FindFunc: method is nil but Interface.Find was just called")
|
||||||
|
}
|
||||||
|
callInfo := struct {
|
||||||
|
Req FindRequest
|
||||||
|
}{
|
||||||
|
Req: req,
|
||||||
|
}
|
||||||
|
mock.lockFind.Lock()
|
||||||
|
mock.calls.Find = append(mock.calls.Find, callInfo)
|
||||||
|
mock.lockFind.Unlock()
|
||||||
|
return mock.FindFunc(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindCalls gets all the calls that were made to Find.
|
||||||
|
// Check the length with:
|
||||||
|
//
|
||||||
|
// len(mockedInterface.FindCalls())
|
||||||
|
func (mock *InterfaceMock) FindCalls() []struct {
|
||||||
|
Req FindRequest
|
||||||
|
} {
|
||||||
|
var calls []struct {
|
||||||
|
Req FindRequest
|
||||||
|
}
|
||||||
|
mock.lockFind.RLock()
|
||||||
|
calls = mock.calls.Find
|
||||||
|
mock.lockFind.RUnlock()
|
||||||
|
return calls
|
||||||
|
}
|
||||||
|
|
||||||
return r0, r1
|
// Flag calls FlagFunc.
|
||||||
|
func (mock *InterfaceMock) Flag(req FlagRequest) (bool, error) {
|
||||||
|
if mock.FlagFunc == nil {
|
||||||
|
panic("InterfaceMock.FlagFunc: method is nil but Interface.Flag was just called")
|
||||||
|
}
|
||||||
|
callInfo := struct {
|
||||||
|
Req FlagRequest
|
||||||
|
}{
|
||||||
|
Req: req,
|
||||||
|
}
|
||||||
|
mock.lockFlag.Lock()
|
||||||
|
mock.calls.Flag = append(mock.calls.Flag, callInfo)
|
||||||
|
mock.lockFlag.Unlock()
|
||||||
|
return mock.FlagFunc(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlagCalls gets all the calls that were made to Flag.
|
||||||
|
// Check the length with:
|
||||||
|
//
|
||||||
|
// len(mockedInterface.FlagCalls())
|
||||||
|
func (mock *InterfaceMock) FlagCalls() []struct {
|
||||||
|
Req FlagRequest
|
||||||
|
} {
|
||||||
|
var calls []struct {
|
||||||
|
Req FlagRequest
|
||||||
|
}
|
||||||
|
mock.lockFlag.RLock()
|
||||||
|
calls = mock.calls.Flag
|
||||||
|
mock.lockFlag.RUnlock()
|
||||||
|
return calls
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get calls GetFunc.
|
||||||
|
func (mock *InterfaceMock) Get(req GetRequest) (store.Comment, error) {
|
||||||
|
if mock.GetFunc == nil {
|
||||||
|
panic("InterfaceMock.GetFunc: method is nil but Interface.Get was just called")
|
||||||
|
}
|
||||||
|
callInfo := struct {
|
||||||
|
Req GetRequest
|
||||||
|
}{
|
||||||
|
Req: req,
|
||||||
|
}
|
||||||
|
mock.lockGet.Lock()
|
||||||
|
mock.calls.Get = append(mock.calls.Get, callInfo)
|
||||||
|
mock.lockGet.Unlock()
|
||||||
|
return mock.GetFunc(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCalls gets all the calls that were made to Get.
|
||||||
|
// Check the length with:
|
||||||
|
//
|
||||||
|
// len(mockedInterface.GetCalls())
|
||||||
|
func (mock *InterfaceMock) GetCalls() []struct {
|
||||||
|
Req GetRequest
|
||||||
|
} {
|
||||||
|
var calls []struct {
|
||||||
|
Req GetRequest
|
||||||
|
}
|
||||||
|
mock.lockGet.RLock()
|
||||||
|
calls = mock.calls.Get
|
||||||
|
mock.lockGet.RUnlock()
|
||||||
|
return calls
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info calls InfoFunc.
|
||||||
|
func (mock *InterfaceMock) Info(req InfoRequest) ([]store.PostInfo, error) {
|
||||||
|
if mock.InfoFunc == nil {
|
||||||
|
panic("InterfaceMock.InfoFunc: method is nil but Interface.Info was just called")
|
||||||
|
}
|
||||||
|
callInfo := struct {
|
||||||
|
Req InfoRequest
|
||||||
|
}{
|
||||||
|
Req: req,
|
||||||
|
}
|
||||||
|
mock.lockInfo.Lock()
|
||||||
|
mock.calls.Info = append(mock.calls.Info, callInfo)
|
||||||
|
mock.lockInfo.Unlock()
|
||||||
|
return mock.InfoFunc(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InfoCalls gets all the calls that were made to Info.
|
||||||
|
// Check the length with:
|
||||||
|
//
|
||||||
|
// len(mockedInterface.InfoCalls())
|
||||||
|
func (mock *InterfaceMock) InfoCalls() []struct {
|
||||||
|
Req InfoRequest
|
||||||
|
} {
|
||||||
|
var calls []struct {
|
||||||
|
Req InfoRequest
|
||||||
|
}
|
||||||
|
mock.lockInfo.RLock()
|
||||||
|
calls = mock.calls.Info
|
||||||
|
mock.lockInfo.RUnlock()
|
||||||
|
return calls
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListFlags calls ListFlagsFunc.
|
||||||
|
func (mock *InterfaceMock) ListFlags(req FlagRequest) ([]interface{}, error) {
|
||||||
|
if mock.ListFlagsFunc == nil {
|
||||||
|
panic("InterfaceMock.ListFlagsFunc: method is nil but Interface.ListFlags was just called")
|
||||||
|
}
|
||||||
|
callInfo := struct {
|
||||||
|
Req FlagRequest
|
||||||
|
}{
|
||||||
|
Req: req,
|
||||||
|
}
|
||||||
|
mock.lockListFlags.Lock()
|
||||||
|
mock.calls.ListFlags = append(mock.calls.ListFlags, callInfo)
|
||||||
|
mock.lockListFlags.Unlock()
|
||||||
|
return mock.ListFlagsFunc(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListFlagsCalls gets all the calls that were made to ListFlags.
|
||||||
|
// Check the length with:
|
||||||
|
//
|
||||||
|
// len(mockedInterface.ListFlagsCalls())
|
||||||
|
func (mock *InterfaceMock) ListFlagsCalls() []struct {
|
||||||
|
Req FlagRequest
|
||||||
|
} {
|
||||||
|
var calls []struct {
|
||||||
|
Req FlagRequest
|
||||||
|
}
|
||||||
|
mock.lockListFlags.RLock()
|
||||||
|
calls = mock.calls.ListFlags
|
||||||
|
mock.lockListFlags.RUnlock()
|
||||||
|
return calls
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update calls UpdateFunc.
|
||||||
|
func (mock *InterfaceMock) Update(comment store.Comment) error {
|
||||||
|
if mock.UpdateFunc == nil {
|
||||||
|
panic("InterfaceMock.UpdateFunc: method is nil but Interface.Update was just called")
|
||||||
|
}
|
||||||
|
callInfo := struct {
|
||||||
|
Comment store.Comment
|
||||||
|
}{
|
||||||
|
Comment: comment,
|
||||||
|
}
|
||||||
|
mock.lockUpdate.Lock()
|
||||||
|
mock.calls.Update = append(mock.calls.Update, callInfo)
|
||||||
|
mock.lockUpdate.Unlock()
|
||||||
|
return mock.UpdateFunc(comment)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCalls gets all the calls that were made to Update.
|
||||||
|
// Check the length with:
|
||||||
|
//
|
||||||
|
// len(mockedInterface.UpdateCalls())
|
||||||
|
func (mock *InterfaceMock) UpdateCalls() []struct {
|
||||||
|
Comment store.Comment
|
||||||
|
} {
|
||||||
|
var calls []struct {
|
||||||
|
Comment store.Comment
|
||||||
|
}
|
||||||
|
mock.lockUpdate.RLock()
|
||||||
|
calls = mock.calls.Update
|
||||||
|
mock.lockUpdate.RUnlock()
|
||||||
|
return calls
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserDetail calls UserDetailFunc.
|
||||||
|
func (mock *InterfaceMock) UserDetail(req UserDetailRequest) ([]UserDetailEntry, error) {
|
||||||
|
if mock.UserDetailFunc == nil {
|
||||||
|
panic("InterfaceMock.UserDetailFunc: method is nil but Interface.UserDetail was just called")
|
||||||
|
}
|
||||||
|
callInfo := struct {
|
||||||
|
Req UserDetailRequest
|
||||||
|
}{
|
||||||
|
Req: req,
|
||||||
|
}
|
||||||
|
mock.lockUserDetail.Lock()
|
||||||
|
mock.calls.UserDetail = append(mock.calls.UserDetail, callInfo)
|
||||||
|
mock.lockUserDetail.Unlock()
|
||||||
|
return mock.UserDetailFunc(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserDetailCalls gets all the calls that were made to UserDetail.
|
||||||
|
// Check the length with:
|
||||||
|
//
|
||||||
|
// len(mockedInterface.UserDetailCalls())
|
||||||
|
func (mock *InterfaceMock) UserDetailCalls() []struct {
|
||||||
|
Req UserDetailRequest
|
||||||
|
} {
|
||||||
|
var calls []struct {
|
||||||
|
Req UserDetailRequest
|
||||||
|
}
|
||||||
|
mock.lockUserDetail.RLock()
|
||||||
|
calls = mock.calls.UserDetail
|
||||||
|
mock.lockUserDetail.RUnlock()
|
||||||
|
return calls
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,14 +71,44 @@ func (r *RPC) Flag(req FlagRequest) (status bool, err error) {
|
|||||||
return status, err
|
return status, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func unmarshalString(data []byte) ([]interface{}, error) {
|
||||||
|
var strings []string
|
||||||
|
if err := json.Unmarshal(data, &strings); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
list := make([]interface{}, 0, len(strings))
|
||||||
|
for _, w := range strings {
|
||||||
|
list = append(list, w)
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalBlockedUser(data []byte) ([]interface{}, error) {
|
||||||
|
var blockedUsers []store.BlockedUser
|
||||||
|
if err := json.Unmarshal(data, &blockedUsers); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
list := make([]interface{}, 0, len(blockedUsers))
|
||||||
|
for _, w := range blockedUsers {
|
||||||
|
list = append(list, w)
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ListFlags get list of flagged keys, like blocked & verified user
|
// ListFlags get list of flagged keys, like blocked & verified user
|
||||||
func (r *RPC) ListFlags(req FlagRequest) (list []interface{}, err error) {
|
func (r *RPC) ListFlags(req FlagRequest) ([]interface{}, error) {
|
||||||
resp, err := r.Call("store.list_flags", req)
|
resp, err := r.Call("store.list_flags", req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(*resp.Result, &list)
|
// try []store.BlockedUser
|
||||||
return list, err
|
list, err := unmarshalBlockedUser(*resp.Result)
|
||||||
|
if err == nil {
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// try []strings
|
||||||
|
return unmarshalString(*resp.Result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserDetail sets or gets single detail value, or gets all details for requested site.
|
// UserDetail sets or gets single detail value, or gets all details for requested site.
|
||||||
|
|||||||
@@ -134,7 +134,13 @@ func TestRemote_ListFlag(t *testing.T) {
|
|||||||
c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}}
|
c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}}
|
||||||
res, err := c.ListFlags(FlagRequest{Locator: store.Locator{SiteID: "site_id"}, Flag: Blocked})
|
res, err := c.ListFlags(FlagRequest{Locator: store.Locator{SiteID: "site_id"}, Flag: Blocked})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, []interface{}{map[string]interface{}{"ID": "id1"}, map[string]interface{}{"ID": "id2"}}, res)
|
require.Equal(t, 2, len(res))
|
||||||
|
blockedUsers := make([]store.BlockedUser, 0, len(res))
|
||||||
|
for _, v := range res {
|
||||||
|
blockedUsers = append(blockedUsers, v.(store.BlockedUser))
|
||||||
|
}
|
||||||
|
assert.Equal(t, 2, len(blockedUsers))
|
||||||
|
assert.Equal(t, []store.BlockedUser{{ID: "id1"}, {ID: "id2"}}, blockedUsers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRemote_UserDetail(t *testing.T) {
|
func TestRemote_UserDetail(t *testing.T) {
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Depado/bfchroma"
|
"github.com/Depado/bfchroma/v2"
|
||||||
"github.com/PuerkitoBio/goquery"
|
"github.com/PuerkitoBio/goquery"
|
||||||
"github.com/alecthomas/chroma/formatters/html"
|
"github.com/alecthomas/chroma/v2/formatters/html"
|
||||||
bf "github.com/russross/blackfriday/v2"
|
bf "github.com/russross/blackfriday/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package image
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -150,7 +149,7 @@ func checkBoltImgData(t *testing.T, db *bolt.DB, bucket, id string, callback fun
|
|||||||
}
|
}
|
||||||
|
|
||||||
func prepareBoltImageStorageTest(t *testing.T) (svc *Bolt, teardown func()) {
|
func prepareBoltImageStorageTest(t *testing.T) (svc *Bolt, teardown func()) {
|
||||||
loc, err := ioutil.TempDir("", "test_image_r42")
|
loc, err := os.MkdirTemp("", "test_image_r42")
|
||||||
require.NoError(t, err, "failed to make temp dir")
|
require.NoError(t, err, "failed to make temp dir")
|
||||||
|
|
||||||
svc, err = NewBoltStorage(path.Join(loc, "picture.db"), bolt.Options{})
|
svc, err = NewBoltStorage(path.Join(loc, "picture.db"), bolt.Options{})
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -181,7 +180,7 @@ func TestFsStore_Cleanup(t *testing.T) {
|
|||||||
svc, teardown := prepareImageTest(t)
|
svc, teardown := prepareImageTest(t)
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
|
||||||
save := func(file string, user string) (filePath string) {
|
save := func(file, user string) (filePath string) {
|
||||||
id := path.Join(user, file)
|
id := path.Join(user, file)
|
||||||
err := svc.Save(id, gopherPNGBytes())
|
err := svc.Save(id, gopherPNGBytes())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -228,7 +227,7 @@ func TestFsStore_Cleanup(t *testing.T) {
|
|||||||
_, err = os.Stat(img2)
|
_, err = os.Stat(img2)
|
||||||
assert.Error(t, err, "no file on staging anymore")
|
assert.Error(t, err, "no file on staging anymore")
|
||||||
_, err = os.Stat(img3)
|
_, err = os.Stat(img3)
|
||||||
assert.NoError(t, err, "third image is still on staging because it's cleanup timer was reset")
|
assert.NoError(t, err, "third image is still on staging because its cleanup timer was reset")
|
||||||
|
|
||||||
err = svc.ResetCleanupTimer("unknown_image.png")
|
err = svc.ResetCleanupTimer("unknown_image.png")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
@@ -254,10 +253,10 @@ func TestFsStore_Info(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func prepareImageTest(t *testing.T) (svc *FileSystem, teardown func()) {
|
func prepareImageTest(t *testing.T) (svc *FileSystem, teardown func()) {
|
||||||
loc, err := ioutil.TempDir("", "test_image_r42")
|
loc, err := os.MkdirTemp("", "test_image_r42")
|
||||||
require.NoError(t, err, "failed to make temp dir")
|
require.NoError(t, err, "failed to make temp dir")
|
||||||
|
|
||||||
staging, err := ioutil.TempDir("", "test_image_r42.staging")
|
staging, err := os.MkdirTemp("", "test_image_r42.staging")
|
||||||
require.NoError(t, err, "failed to make temp staging dir")
|
require.NoError(t, err, "failed to make temp staging dir")
|
||||||
|
|
||||||
svc = &FileSystem{
|
svc = &FileSystem{
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
// Service object encloses Store and add common methods, this is the one consumer should use.
|
// Service object encloses Store and add common methods, this is the one consumer should use.
|
||||||
package image
|
package image
|
||||||
|
|
||||||
//go:generate sh -c "mockery -inpkg -name Store -print > /tmp/mock.tmp && mv /tmp/mock.tmp image_mock.go"
|
// NOTE: matryer/moq should be installed globally and works with `go generate ./...`
|
||||||
|
//go:generate moq --out image_mock.go . Store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -61,9 +62,6 @@ type StoreInfo struct {
|
|||||||
FirstStagingImageTS time.Time
|
FirstStagingImageTS time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// To regenerate mock run from this directory:
|
|
||||||
// sh -c "mockery -inpkg -name Store -print > /tmp/image-mock.tmp && mv /tmp/image-mock.tmp image_mock.go"
|
|
||||||
|
|
||||||
// Store defines interface for saving and loading pictures.
|
// Store defines interface for saving and loading pictures.
|
||||||
// Declares two-stage save with Commit. Save stores to staging area and Commit moves to the final location.
|
// Declares two-stage save with Commit. Save stores to staging area and Commit moves to the final location.
|
||||||
// Two-stage commit scheme is used for not storing images which are uploaded but later never used in the comments,
|
// Two-stage commit scheme is used for not storing images which are uploaded but later never used in the comments,
|
||||||
@@ -367,7 +365,7 @@ func Sha1Str(s string) string {
|
|||||||
// ID would look like: "cached_images/<sha1-of-image-url-hostname>-<sha1-of-image-entire-url>"
|
// ID would look like: "cached_images/<sha1-of-image-url-hostname>-<sha1-of-image-entire-url>"
|
||||||
// <sha1-of-image-url-hostname> - would allow us to identify all images from particular site if ever needed
|
// <sha1-of-image-url-hostname> - would allow us to identify all images from particular site if ever needed
|
||||||
// <sha1-of-image-entire-url> - would allow us to avoid storing duplicates of the same image
|
// <sha1-of-image-entire-url> - would allow us to avoid storing duplicates of the same image
|
||||||
// (as accurate as deduplication based on potentially mutable url can be)
|
// (as accurate as deduplication based on potentially mutable url can be)
|
||||||
func CachedImgID(imgURL string) (string, error) {
|
func CachedImgID(imgURL string) (string, error) {
|
||||||
parsedURL, err := url.Parse(imgURL)
|
parsedURL, err := url.Parse(imgURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,115 +1,301 @@
|
|||||||
// Code generated by mockery v1.1.2. DO NOT EDIT.
|
// Code generated by moq; DO NOT EDIT.
|
||||||
|
// github.com/matryer/moq
|
||||||
|
|
||||||
package image
|
package image
|
||||||
|
|
||||||
import (
|
import (
|
||||||
context "context"
|
context "context"
|
||||||
|
"sync"
|
||||||
time "time"
|
time "time"
|
||||||
|
|
||||||
mock "github.com/stretchr/testify/mock"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockStore is an autogenerated mock type for the Store type
|
// Ensure, that StoreMock does implement Store.
|
||||||
type MockStore struct {
|
// If this is not the case, regenerate this file with moq.
|
||||||
mock.Mock
|
var _ Store = &StoreMock{}
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup provides a mock function with given fields: ctx, ttl
|
// StoreMock is a mock implementation of Store.
|
||||||
func (_m *MockStore) Cleanup(ctx context.Context, ttl time.Duration) error {
|
//
|
||||||
ret := _m.Called(ctx, ttl)
|
// func TestSomethingThatUsesStore(t *testing.T) {
|
||||||
|
//
|
||||||
|
// // make and configure a mocked Store
|
||||||
|
// mockedStore := &StoreMock{
|
||||||
|
// CleanupFunc: func(ctx context.Context, ttl time.Duration) error {
|
||||||
|
// panic("mock out the Cleanup method")
|
||||||
|
// },
|
||||||
|
// CommitFunc: func(id string) error {
|
||||||
|
// panic("mock out the Commit method")
|
||||||
|
// },
|
||||||
|
// InfoFunc: func() (StoreInfo, error) {
|
||||||
|
// panic("mock out the Info method")
|
||||||
|
// },
|
||||||
|
// LoadFunc: func(id string) ([]byte, error) {
|
||||||
|
// panic("mock out the Load method")
|
||||||
|
// },
|
||||||
|
// ResetCleanupTimerFunc: func(id string) error {
|
||||||
|
// panic("mock out the ResetCleanupTimer method")
|
||||||
|
// },
|
||||||
|
// SaveFunc: func(id string, img []byte) error {
|
||||||
|
// panic("mock out the Save method")
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // use mockedStore in code that requires Store
|
||||||
|
// // and then make assertions.
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
type StoreMock struct {
|
||||||
|
// CleanupFunc mocks the Cleanup method.
|
||||||
|
CleanupFunc func(ctx context.Context, ttl time.Duration) error
|
||||||
|
|
||||||
var r0 error
|
// CommitFunc mocks the Commit method.
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, time.Duration) error); ok {
|
CommitFunc func(id string) error
|
||||||
r0 = rf(ctx, ttl)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
// InfoFunc mocks the Info method.
|
||||||
}
|
InfoFunc func() (StoreInfo, error)
|
||||||
|
|
||||||
// Commit provides a mock function with given fields: id
|
// LoadFunc mocks the Load method.
|
||||||
func (_m *MockStore) Commit(id string) error {
|
LoadFunc func(id string) ([]byte, error)
|
||||||
ret := _m.Called(id)
|
|
||||||
|
|
||||||
var r0 error
|
// ResetCleanupTimerFunc mocks the ResetCleanupTimer method.
|
||||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
ResetCleanupTimerFunc func(id string) error
|
||||||
r0 = rf(id)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
// SaveFunc mocks the Save method.
|
||||||
}
|
SaveFunc func(id string, img []byte) error
|
||||||
|
|
||||||
// Info provides a mock function with given fields:
|
// calls tracks calls to the methods.
|
||||||
func (_m *MockStore) Info() (StoreInfo, error) {
|
calls struct {
|
||||||
ret := _m.Called()
|
// Cleanup holds details about calls to the Cleanup method.
|
||||||
|
Cleanup []struct {
|
||||||
var r0 StoreInfo
|
// Ctx is the ctx argument value.
|
||||||
if rf, ok := ret.Get(0).(func() StoreInfo); ok {
|
Ctx context.Context
|
||||||
r0 = rf()
|
// TTL is the ttl argument value.
|
||||||
} else {
|
TTL time.Duration
|
||||||
r0 = ret.Get(0).(StoreInfo)
|
}
|
||||||
}
|
// Commit holds details about calls to the Commit method.
|
||||||
|
Commit []struct {
|
||||||
var r1 error
|
// ID is the id argument value.
|
||||||
if rf, ok := ret.Get(1).(func() error); ok {
|
ID string
|
||||||
r1 = rf()
|
}
|
||||||
} else {
|
// Info holds details about calls to the Info method.
|
||||||
r1 = ret.Error(1)
|
Info []struct {
|
||||||
}
|
}
|
||||||
|
// Load holds details about calls to the Load method.
|
||||||
return r0, r1
|
Load []struct {
|
||||||
}
|
// ID is the id argument value.
|
||||||
|
ID string
|
||||||
// Load provides a mock function with given fields: id
|
}
|
||||||
func (_m *MockStore) Load(id string) ([]byte, error) {
|
// ResetCleanupTimer holds details about calls to the ResetCleanupTimer method.
|
||||||
ret := _m.Called(id)
|
ResetCleanupTimer []struct {
|
||||||
|
// ID is the id argument value.
|
||||||
var r0 []byte
|
ID string
|
||||||
if rf, ok := ret.Get(0).(func(string) []byte); ok {
|
}
|
||||||
r0 = rf(id)
|
// Save holds details about calls to the Save method.
|
||||||
} else {
|
Save []struct {
|
||||||
if ret.Get(0) != nil {
|
// ID is the id argument value.
|
||||||
r0 = ret.Get(0).([]byte)
|
ID string
|
||||||
|
// Img is the img argument value.
|
||||||
|
Img []byte
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
lockCleanup sync.RWMutex
|
||||||
var r1 error
|
lockCommit sync.RWMutex
|
||||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
lockInfo sync.RWMutex
|
||||||
r1 = rf(id)
|
lockLoad sync.RWMutex
|
||||||
} else {
|
lockResetCleanupTimer sync.RWMutex
|
||||||
r1 = ret.Error(1)
|
lockSave sync.RWMutex
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetCleanupTimer provides a mock function with given fields: id
|
// Cleanup calls CleanupFunc.
|
||||||
func (_m *MockStore) ResetCleanupTimer(id string) error {
|
func (mock *StoreMock) Cleanup(ctx context.Context, ttl time.Duration) error {
|
||||||
ret := _m.Called(id)
|
if mock.CleanupFunc == nil {
|
||||||
|
panic("StoreMock.CleanupFunc: method is nil but Store.Cleanup was just called")
|
||||||
var r0 error
|
|
||||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
|
||||||
r0 = rf(id)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
}
|
||||||
|
callInfo := struct {
|
||||||
return r0
|
Ctx context.Context
|
||||||
|
TTL time.Duration
|
||||||
|
}{
|
||||||
|
Ctx: ctx,
|
||||||
|
TTL: ttl,
|
||||||
|
}
|
||||||
|
mock.lockCleanup.Lock()
|
||||||
|
mock.calls.Cleanup = append(mock.calls.Cleanup, callInfo)
|
||||||
|
mock.lockCleanup.Unlock()
|
||||||
|
return mock.CleanupFunc(ctx, ttl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save provides a mock function with given fields: id, img
|
// CleanupCalls gets all the calls that were made to Cleanup.
|
||||||
func (_m *MockStore) Save(id string, img []byte) error {
|
// Check the length with:
|
||||||
ret := _m.Called(id, img)
|
//
|
||||||
|
// len(mockedStore.CleanupCalls())
|
||||||
var r0 error
|
func (mock *StoreMock) CleanupCalls() []struct {
|
||||||
if rf, ok := ret.Get(0).(func(string, []byte) error); ok {
|
Ctx context.Context
|
||||||
r0 = rf(id, img)
|
TTL time.Duration
|
||||||
} else {
|
} {
|
||||||
r0 = ret.Error(0)
|
var calls []struct {
|
||||||
|
Ctx context.Context
|
||||||
|
TTL time.Duration
|
||||||
}
|
}
|
||||||
|
mock.lockCleanup.RLock()
|
||||||
|
calls = mock.calls.Cleanup
|
||||||
|
mock.lockCleanup.RUnlock()
|
||||||
|
return calls
|
||||||
|
}
|
||||||
|
|
||||||
return r0
|
// Commit calls CommitFunc.
|
||||||
|
func (mock *StoreMock) Commit(id string) error {
|
||||||
|
if mock.CommitFunc == nil {
|
||||||
|
panic("StoreMock.CommitFunc: method is nil but Store.Commit was just called")
|
||||||
|
}
|
||||||
|
callInfo := struct {
|
||||||
|
ID string
|
||||||
|
}{
|
||||||
|
ID: id,
|
||||||
|
}
|
||||||
|
mock.lockCommit.Lock()
|
||||||
|
mock.calls.Commit = append(mock.calls.Commit, callInfo)
|
||||||
|
mock.lockCommit.Unlock()
|
||||||
|
return mock.CommitFunc(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitCalls gets all the calls that were made to Commit.
|
||||||
|
// Check the length with:
|
||||||
|
//
|
||||||
|
// len(mockedStore.CommitCalls())
|
||||||
|
func (mock *StoreMock) CommitCalls() []struct {
|
||||||
|
ID string
|
||||||
|
} {
|
||||||
|
var calls []struct {
|
||||||
|
ID string
|
||||||
|
}
|
||||||
|
mock.lockCommit.RLock()
|
||||||
|
calls = mock.calls.Commit
|
||||||
|
mock.lockCommit.RUnlock()
|
||||||
|
return calls
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info calls InfoFunc.
|
||||||
|
func (mock *StoreMock) Info() (StoreInfo, error) {
|
||||||
|
if mock.InfoFunc == nil {
|
||||||
|
panic("StoreMock.InfoFunc: method is nil but Store.Info was just called")
|
||||||
|
}
|
||||||
|
callInfo := struct {
|
||||||
|
}{}
|
||||||
|
mock.lockInfo.Lock()
|
||||||
|
mock.calls.Info = append(mock.calls.Info, callInfo)
|
||||||
|
mock.lockInfo.Unlock()
|
||||||
|
return mock.InfoFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// InfoCalls gets all the calls that were made to Info.
|
||||||
|
// Check the length with:
|
||||||
|
//
|
||||||
|
// len(mockedStore.InfoCalls())
|
||||||
|
func (mock *StoreMock) InfoCalls() []struct {
|
||||||
|
} {
|
||||||
|
var calls []struct {
|
||||||
|
}
|
||||||
|
mock.lockInfo.RLock()
|
||||||
|
calls = mock.calls.Info
|
||||||
|
mock.lockInfo.RUnlock()
|
||||||
|
return calls
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load calls LoadFunc.
|
||||||
|
func (mock *StoreMock) Load(id string) ([]byte, error) {
|
||||||
|
if mock.LoadFunc == nil {
|
||||||
|
panic("StoreMock.LoadFunc: method is nil but Store.Load was just called")
|
||||||
|
}
|
||||||
|
callInfo := struct {
|
||||||
|
ID string
|
||||||
|
}{
|
||||||
|
ID: id,
|
||||||
|
}
|
||||||
|
mock.lockLoad.Lock()
|
||||||
|
mock.calls.Load = append(mock.calls.Load, callInfo)
|
||||||
|
mock.lockLoad.Unlock()
|
||||||
|
return mock.LoadFunc(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadCalls gets all the calls that were made to Load.
|
||||||
|
// Check the length with:
|
||||||
|
//
|
||||||
|
// len(mockedStore.LoadCalls())
|
||||||
|
func (mock *StoreMock) LoadCalls() []struct {
|
||||||
|
ID string
|
||||||
|
} {
|
||||||
|
var calls []struct {
|
||||||
|
ID string
|
||||||
|
}
|
||||||
|
mock.lockLoad.RLock()
|
||||||
|
calls = mock.calls.Load
|
||||||
|
mock.lockLoad.RUnlock()
|
||||||
|
return calls
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetCleanupTimer calls ResetCleanupTimerFunc.
|
||||||
|
func (mock *StoreMock) ResetCleanupTimer(id string) error {
|
||||||
|
if mock.ResetCleanupTimerFunc == nil {
|
||||||
|
panic("StoreMock.ResetCleanupTimerFunc: method is nil but Store.ResetCleanupTimer was just called")
|
||||||
|
}
|
||||||
|
callInfo := struct {
|
||||||
|
ID string
|
||||||
|
}{
|
||||||
|
ID: id,
|
||||||
|
}
|
||||||
|
mock.lockResetCleanupTimer.Lock()
|
||||||
|
mock.calls.ResetCleanupTimer = append(mock.calls.ResetCleanupTimer, callInfo)
|
||||||
|
mock.lockResetCleanupTimer.Unlock()
|
||||||
|
return mock.ResetCleanupTimerFunc(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetCleanupTimerCalls gets all the calls that were made to ResetCleanupTimer.
|
||||||
|
// Check the length with:
|
||||||
|
//
|
||||||
|
// len(mockedStore.ResetCleanupTimerCalls())
|
||||||
|
func (mock *StoreMock) ResetCleanupTimerCalls() []struct {
|
||||||
|
ID string
|
||||||
|
} {
|
||||||
|
var calls []struct {
|
||||||
|
ID string
|
||||||
|
}
|
||||||
|
mock.lockResetCleanupTimer.RLock()
|
||||||
|
calls = mock.calls.ResetCleanupTimer
|
||||||
|
mock.lockResetCleanupTimer.RUnlock()
|
||||||
|
return calls
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save calls SaveFunc.
|
||||||
|
func (mock *StoreMock) Save(id string, img []byte) error {
|
||||||
|
if mock.SaveFunc == nil {
|
||||||
|
panic("StoreMock.SaveFunc: method is nil but Store.Save was just called")
|
||||||
|
}
|
||||||
|
callInfo := struct {
|
||||||
|
ID string
|
||||||
|
Img []byte
|
||||||
|
}{
|
||||||
|
ID: id,
|
||||||
|
Img: img,
|
||||||
|
}
|
||||||
|
mock.lockSave.Lock()
|
||||||
|
mock.calls.Save = append(mock.calls.Save, callInfo)
|
||||||
|
mock.lockSave.Unlock()
|
||||||
|
return mock.SaveFunc(id, img)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveCalls gets all the calls that were made to Save.
|
||||||
|
// Check the length with:
|
||||||
|
//
|
||||||
|
// len(mockedStore.SaveCalls())
|
||||||
|
func (mock *StoreMock) SaveCalls() []struct {
|
||||||
|
ID string
|
||||||
|
Img []byte
|
||||||
|
} {
|
||||||
|
var calls []struct {
|
||||||
|
ID string
|
||||||
|
Img []byte
|
||||||
|
}
|
||||||
|
mock.lockSave.RLock()
|
||||||
|
calls = mock.calls.Save
|
||||||
|
mock.lockSave.RUnlock()
|
||||||
|
return calls
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,22 +14,30 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestService_SaveAndLoad(t *testing.T) {
|
func TestService_SaveAndLoad(t *testing.T) {
|
||||||
store := MockStore{}
|
store := StoreMock{
|
||||||
|
SaveFunc: func(id string, img []byte) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
LoadFunc: func(id string) ([]byte, error) {
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
svc := NewService(&store, ServiceParams{MaxSize: 1500, MaxWidth: 32, MaxHeight: 32})
|
svc := NewService(&store, ServiceParams{MaxSize: 1500, MaxWidth: 32, MaxHeight: 32})
|
||||||
|
|
||||||
store.On("Save", "test_id", mock.Anything).Return(nil)
|
|
||||||
err := svc.SaveWithID("test_id", gopherPNG())
|
err := svc.SaveWithID("test_id", gopherPNG())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, len(store.SaveCalls()))
|
||||||
|
assert.Equal(t, "test_id", store.SaveCalls()[0].ID)
|
||||||
|
|
||||||
store.On("Load", "test_id", mock.Anything).Return(nil, nil)
|
|
||||||
img, err := svc.Load("test_id")
|
img, err := svc.Load("test_id")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Nil(t, img)
|
assert.Nil(t, img)
|
||||||
|
assert.Equal(t, 1, len(store.LoadCalls()))
|
||||||
|
assert.Equal(t, "test_id", store.LoadCalls()[0].ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestService_Resize(t *testing.T) {
|
func TestService_Resize(t *testing.T) {
|
||||||
@@ -116,72 +124,91 @@ func TestService_ExtractPictures(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestService_Cleanup(t *testing.T) {
|
func TestService_Cleanup(t *testing.T) {
|
||||||
store := MockStore{}
|
store := StoreMock{
|
||||||
store.On("Cleanup", mock.Anything, mock.Anything).Times(10).Return(nil)
|
CleanupFunc: func(ctx context.Context, ttl time.Duration) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
svc := NewService(&store, ServiceParams{EditDuration: 20 * time.Millisecond})
|
svc := NewService(&store, ServiceParams{EditDuration: 20 * time.Millisecond})
|
||||||
// cancel context after 2.1 cleanup TTLs
|
// cancel context after 2.1 cleanup TTLs
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), svc.EditDuration/100*15*21)
|
ctx, cancel := context.WithTimeout(context.Background(), svc.EditDuration/100*15*21)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
svc.Cleanup(ctx)
|
svc.Cleanup(ctx)
|
||||||
store.AssertNumberOfCalls(t, "Cleanup", 2)
|
assert.Equal(t, 2, len(store.CleanupCalls()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestService_Submit(t *testing.T) {
|
func TestService_Submit(t *testing.T) {
|
||||||
store := MockStore{}
|
store := StoreMock{
|
||||||
store.On("Commit", mock.Anything, mock.Anything).Times(7).Return(nil)
|
CommitFunc: func(id string) error {
|
||||||
store.On("ResetCleanupTimer", mock.Anything, mock.Anything).Times(7).Return(nil)
|
return nil
|
||||||
|
},
|
||||||
|
ResetCleanupTimerFunc: func(id string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
svc := NewService(&store, ServiceParams{ImageAPI: "/blah/", EditDuration: time.Millisecond * 100})
|
svc := NewService(&store, ServiceParams{ImageAPI: "/blah/", EditDuration: time.Millisecond * 100})
|
||||||
svc.Submit(func() []string { return []string{"id1", "id2", "id3"} })
|
svc.Submit(func() []string { return []string{"id1", "id2", "id3"} })
|
||||||
store.AssertNumberOfCalls(t, "ResetCleanupTimer", 3)
|
assert.Equal(t, 3, len(store.ResetCleanupTimerCalls()))
|
||||||
err := svc.SubmitAndCommit(func() []string { return []string{"id4", "id5"} })
|
err := svc.SubmitAndCommit(func() []string { return []string{"id4", "id5"} })
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
svc.Submit(func() []string { return []string{"id6", "id7"} })
|
svc.Submit(func() []string { return []string{"id6", "id7"} })
|
||||||
store.AssertNumberOfCalls(t, "ResetCleanupTimer", 5)
|
assert.Equal(t, 5, len(store.ResetCleanupTimerCalls()))
|
||||||
svc.Submit(nil)
|
svc.Submit(nil)
|
||||||
store.AssertNumberOfCalls(t, "Commit", 2)
|
assert.Equal(t, 2, len(store.CommitCalls()))
|
||||||
time.Sleep(time.Millisecond * 175)
|
time.Sleep(time.Millisecond * 175)
|
||||||
store.AssertNumberOfCalls(t, "Commit", 7)
|
assert.Equal(t, 7, len(store.CommitCalls()))
|
||||||
svc.Close(context.TODO())
|
svc.Close(context.TODO())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestService_Close(t *testing.T) {
|
func TestService_Close(t *testing.T) {
|
||||||
store := MockStore{}
|
store := StoreMock{
|
||||||
store.On("Commit", mock.Anything, mock.Anything).Times(5).Return(nil)
|
CommitFunc: func(id string) error {
|
||||||
store.On("ResetCleanupTimer", mock.Anything, mock.Anything).Times(5).Return(nil)
|
return nil
|
||||||
|
},
|
||||||
|
ResetCleanupTimerFunc: func(id string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
svc := Service{store: &store, ServiceParams: ServiceParams{ImageAPI: "/blah/", EditDuration: time.Hour * 24}}
|
svc := Service{store: &store, ServiceParams: ServiceParams{ImageAPI: "/blah/", EditDuration: time.Hour * 24}}
|
||||||
svc.Submit(func() []string { return []string{"id1", "id2", "id3"} })
|
svc.Submit(func() []string { return []string{"id1", "id2", "id3"} })
|
||||||
svc.Submit(func() []string { return []string{"id4", "id5"} })
|
svc.Submit(func() []string { return []string{"id4", "id5"} })
|
||||||
svc.Submit(nil)
|
svc.Submit(nil)
|
||||||
store.AssertNumberOfCalls(t, "ResetCleanupTimer", 5)
|
assert.Equal(t, 5, len(store.ResetCleanupTimerCalls()))
|
||||||
svc.Close(context.TODO())
|
svc.Close(context.TODO())
|
||||||
store.AssertNumberOfCalls(t, "Commit", 5)
|
assert.Equal(t, 5, len(store.CommitCalls()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestService_SubmitDelay(t *testing.T) {
|
func TestService_SubmitDelay(t *testing.T) {
|
||||||
store := MockStore{}
|
store := StoreMock{
|
||||||
store.On("Commit", mock.Anything, mock.Anything).Times(5).Return(nil)
|
CommitFunc: func(id string) error {
|
||||||
store.On("ResetCleanupTimer", mock.Anything, mock.Anything).Times(5).Return(nil)
|
return nil
|
||||||
|
},
|
||||||
|
ResetCleanupTimerFunc: func(id string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
svc := NewService(&store, ServiceParams{EditDuration: 20 * time.Millisecond})
|
svc := NewService(&store, ServiceParams{EditDuration: 20 * time.Millisecond})
|
||||||
svc.Submit(func() []string { return []string{"id1", "id2", "id3"} })
|
svc.Submit(func() []string { return []string{"id1", "id2", "id3"} })
|
||||||
time.Sleep(150 * time.Millisecond) // let first batch to pass TTL
|
time.Sleep(150 * time.Millisecond) // let first batch to pass TTL
|
||||||
svc.Submit(func() []string { return []string{"id4", "id5"} })
|
svc.Submit(func() []string { return []string{"id4", "id5"} })
|
||||||
svc.Submit(nil)
|
svc.Submit(nil)
|
||||||
store.AssertNumberOfCalls(t, "ResetCleanupTimer", 5)
|
assert.Equal(t, 5, len(store.ResetCleanupTimerCalls()))
|
||||||
store.AssertNumberOfCalls(t, "Commit", 3)
|
assert.Equal(t, 3, len(store.CommitCalls()))
|
||||||
svc.Close(context.TODO())
|
svc.Close(context.TODO())
|
||||||
store.AssertNumberOfCalls(t, "Commit", 5)
|
assert.Equal(t, 5, len(store.CommitCalls()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestService_Info(t *testing.T) {
|
func TestService_Info(t *testing.T) {
|
||||||
store := MockStore{}
|
store := StoreMock{InfoFunc: func() (StoreInfo, error) {
|
||||||
store.On("Info", mock.Anything, mock.Anything).Once().Return(StoreInfo{}, nil)
|
return StoreInfo{}, nil
|
||||||
|
}}
|
||||||
|
|
||||||
svc := Service{store: &store, ServiceParams: ServiceParams{}}
|
svc := Service{store: &store, ServiceParams: ServiceParams{}}
|
||||||
info, err := svc.Info()
|
info, err := svc.Info()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, info.FirstStagingImageTS.IsZero())
|
assert.True(t, info.FirstStagingImageTS.IsZero())
|
||||||
store.AssertNumberOfCalls(t, "Info", 1)
|
assert.Equal(t, 1, len(store.InfoCalls()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestService_resize(t *testing.T) {
|
func TestService_resize(t *testing.T) {
|
||||||
|
|||||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 5.1 KiB |
@@ -496,6 +496,12 @@ func (s *DataStore) EditComment(locator store.Locator, commentID string, req Edi
|
|||||||
if e := s.AdminStore.OnEvent(comment.Locator.SiteID, admin.EvDelete); e != nil {
|
if e := s.AdminStore.OnEvent(comment.Locator.SiteID, admin.EvDelete); e != nil {
|
||||||
log.Printf("[WARN] failed to send delete event, %s", e)
|
log.Printf("[WARN] failed to send delete event, %s", e)
|
||||||
}
|
}
|
||||||
|
// clean up the comment and its parent from cache, so that
|
||||||
|
// after cleaning up the child, parent won't be stuck non-deletable till cache expires
|
||||||
|
if s.repliesCache.LoadingCache != nil {
|
||||||
|
s.repliesCache.Delete(commentID)
|
||||||
|
s.repliesCache.Delete(comment.ParentID)
|
||||||
|
}
|
||||||
comment.Deleted = true
|
comment.Deleted = true
|
||||||
delReq := engine.DeleteRequest{Locator: locator, CommentID: commentID, DeleteMode: store.SoftDelete}
|
delReq := engine.DeleteRequest{Locator: locator, CommentID: commentID, DeleteMode: store.SoftDelete}
|
||||||
return comment, s.Engine.Delete(delReq)
|
return comment, s.Engine.Delete(delReq)
|
||||||
@@ -739,6 +745,17 @@ func (s *DataStore) Delete(locator store.Locator, commentID string, mode store.D
|
|||||||
if e := s.AdminStore.OnEvent(locator.SiteID, admin.EvDelete); e != nil {
|
if e := s.AdminStore.OnEvent(locator.SiteID, admin.EvDelete); e != nil {
|
||||||
log.Printf("[WARN] failed to send delete event, %s", e)
|
log.Printf("[WARN] failed to send delete event, %s", e)
|
||||||
}
|
}
|
||||||
|
// get comment to learn its parent ID
|
||||||
|
comment, err := s.Engine.Get(engine.GetRequest{Locator: locator, CommentID: commentID})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// clean up the comment and its parent from cache, so that
|
||||||
|
// after cleaning up the child, parent won't be stuck non-deletable till cache expires
|
||||||
|
if s.repliesCache.LoadingCache != nil {
|
||||||
|
s.repliesCache.Delete(commentID)
|
||||||
|
s.repliesCache.Delete(comment.ParentID)
|
||||||
|
}
|
||||||
req := engine.DeleteRequest{Locator: locator, CommentID: commentID, DeleteMode: mode}
|
req := engine.DeleteRequest{Locator: locator, CommentID: commentID, DeleteMode: mode}
|
||||||
return s.Engine.Delete(req)
|
return s.Engine.Delete(req)
|
||||||
}
|
}
|
||||||
@@ -981,9 +998,9 @@ func (s *DataStore) prepVotes(c store.Comment, user store.User) store.Comment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get secret for given siteID
|
// get secret for given siteID
|
||||||
// Note: secret shared across sites, but some sites can be disabled.
|
// Note: siteID ignored for the default admin.Static store
|
||||||
func (s *DataStore) getSecret(siteID string) (secret string, err error) {
|
func (s *DataStore) getSecret(siteID string) (secret string, err error) {
|
||||||
if secret, err = s.AdminStore.Key("any"); err != nil {
|
if secret, err = s.AdminStore.Key(siteID); err != nil {
|
||||||
return "", fmt.Errorf("can't get secret for site %s: %w", siteID, err)
|
return "", fmt.Errorf("can't get secret for site %s: %w", siteID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@@ -972,7 +971,7 @@ func TestService_HasReplies(t *testing.T) {
|
|||||||
// two comments for https://radio-t.com, no reply
|
// two comments for https://radio-t.com, no reply
|
||||||
eng, teardown := prepStoreEngine(t)
|
eng, teardown := prepStoreEngine(t)
|
||||||
defer teardown()
|
defer teardown()
|
||||||
b := DataStore{Engine: eng, EditDuration: 100 * time.Millisecond,
|
b := DataStore{Engine: eng,
|
||||||
AdminStore: admin.NewStaticStore("secret 123", []string{"radio-t"}, []string{"user2"}, "user@email.com")}
|
AdminStore: admin.NewStaticStore("secret 123", []string{"radio-t"}, []string{"user2"}, "user@email.com")}
|
||||||
defer b.Close()
|
defer b.Close()
|
||||||
|
|
||||||
@@ -983,11 +982,10 @@ func TestService_HasReplies(t *testing.T) {
|
|||||||
Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"},
|
Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"},
|
||||||
User: store.User{ID: "user1", Name: "user name"},
|
User: store.User{ID: "user1", Name: "user name"},
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.False(t, b.HasReplies(comment))
|
assert.False(t, b.HasReplies(comment))
|
||||||
|
|
||||||
reply := store.Comment{
|
reply := store.Comment{
|
||||||
ID: "123456",
|
ID: "c-1",
|
||||||
ParentID: "id-1",
|
ParentID: "id-1",
|
||||||
Text: "some text",
|
Text: "some text",
|
||||||
Timestamp: time.Date(2017, 12, 20, 15, 18, 22, 0, time.Local),
|
Timestamp: time.Date(2017, 12, 20, 15, 18, 22, 0, time.Local),
|
||||||
@@ -996,7 +994,45 @@ func TestService_HasReplies(t *testing.T) {
|
|||||||
}
|
}
|
||||||
_, err := b.Create(reply)
|
_, err := b.Create(reply)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
_, found := b.repliesCache.Peek(comment.ID)
|
||||||
|
assert.False(t, found, "not yet checked")
|
||||||
assert.True(t, b.HasReplies(comment))
|
assert.True(t, b.HasReplies(comment))
|
||||||
|
_, found = b.repliesCache.Peek(reply.ParentID)
|
||||||
|
assert.True(t, found, "checked and has replies")
|
||||||
|
|
||||||
|
// deletion of the parent comment shouldn't work as the comment has replies
|
||||||
|
_, err = b.EditComment(reply.Locator, comment.ID, EditRequest{Orig: comment.ID, Delete: true, Summary: "user deletes the comment"})
|
||||||
|
assert.EqualError(t, err, "parent comment with reply can't be edited, "+comment.ID)
|
||||||
|
_, found = b.repliesCache.Peek(reply.ParentID)
|
||||||
|
assert.True(t, found, "checked and has replies")
|
||||||
|
|
||||||
|
// should not report replies after deletion of the child
|
||||||
|
err = b.Delete(reply.Locator, reply.ID, store.HardDelete)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, found = b.repliesCache.Peek(reply.ParentID)
|
||||||
|
assert.False(t, found, "cleaned up from cache by Delete call")
|
||||||
|
assert.False(t, b.HasReplies(comment))
|
||||||
|
_, found = b.repliesCache.Peek(reply.ParentID)
|
||||||
|
assert.False(t, found, "checked and has no replies")
|
||||||
|
|
||||||
|
// recreate reply with the new ID
|
||||||
|
reply.ID = "c-2"
|
||||||
|
_, err = b.Create(reply)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, found = b.repliesCache.Peek(reply.ParentID)
|
||||||
|
assert.False(t, found, "not yet checked")
|
||||||
|
assert.True(t, b.HasReplies(comment))
|
||||||
|
_, found = b.repliesCache.Peek(reply.ParentID)
|
||||||
|
assert.True(t, found, "checked and has replies again")
|
||||||
|
|
||||||
|
// should not report replies after deletion of the child using Edit mechanism
|
||||||
|
_, err = b.EditComment(reply.Locator, reply.ID, EditRequest{Orig: reply.ID, Delete: true, Summary: "user deletes the comment"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, found = b.repliesCache.Peek(reply.ParentID)
|
||||||
|
assert.False(t, found, "cleaned up from cache by EditComment call")
|
||||||
|
assert.False(t, b.HasReplies(comment))
|
||||||
|
_, found = b.repliesCache.Peek(reply.ParentID)
|
||||||
|
assert.False(t, found, "checked and has no replies")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestService_UserReplies(t *testing.T) {
|
func TestService_UserReplies(t *testing.T) {
|
||||||
@@ -1359,11 +1395,10 @@ func TestService_DeleteAll(t *testing.T) {
|
|||||||
func TestService_submitImages(t *testing.T) {
|
func TestService_submitImages(t *testing.T) {
|
||||||
lgr.Setup(lgr.Debug, lgr.CallerFile, lgr.CallerFunc)
|
lgr.Setup(lgr.Debug, lgr.CallerFile, lgr.CallerFunc)
|
||||||
|
|
||||||
mockStore := image.MockStore{}
|
mockStore := image.StoreMock{
|
||||||
mockStore.On("Commit", "dev/pic1.png").Once().Return(nil)
|
CommitFunc: func(id string) error { return nil },
|
||||||
mockStore.On("Commit", "dev/pic2.png").Once().Return(nil)
|
ResetCleanupTimerFunc: func(id string) error { return nil },
|
||||||
mockStore.On("ResetCleanupTimer", "dev/pic1.png").Once().Return(nil)
|
}
|
||||||
mockStore.On("ResetCleanupTimer", "dev/pic2.png").Once().Return(nil)
|
|
||||||
imgSvc := image.NewService(&mockStore,
|
imgSvc := image.NewService(&mockStore,
|
||||||
image.ServiceParams{
|
image.ServiceParams{
|
||||||
EditDuration: 50 * time.Millisecond,
|
EditDuration: 50 * time.Millisecond,
|
||||||
@@ -1389,13 +1424,25 @@ func TestService_submitImages(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
b.submitImages(c)
|
b.submitImages(c)
|
||||||
mockStore.AssertNumberOfCalls(t, "ResetCleanupTimer", 2)
|
assert.Equal(t, 2, len(mockStore.ResetCleanupTimerCalls()))
|
||||||
|
assert.Equal(t, "dev/pic1.png", mockStore.ResetCleanupTimerCalls()[0].ID)
|
||||||
|
assert.Equal(t, "dev/pic2.png", mockStore.ResetCleanupTimerCalls()[1].ID)
|
||||||
time.Sleep(b.EditDuration + 100*time.Millisecond)
|
time.Sleep(b.EditDuration + 100*time.Millisecond)
|
||||||
mockStore.AssertNumberOfCalls(t, "Commit", 2)
|
assert.Equal(t, 2, len(mockStore.CommitCalls()))
|
||||||
|
assert.Equal(t, "dev/pic1.png", mockStore.CommitCalls()[0].ID)
|
||||||
|
assert.Equal(t, "dev/pic2.png", mockStore.CommitCalls()[1].ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestService_ResubmitStagingImages(t *testing.T) {
|
func TestService_ResubmitStagingImages(t *testing.T) {
|
||||||
mockStore := image.MockStore{}
|
mockStore := image.StoreMock{
|
||||||
|
InfoFunc: func() (image.StoreInfo, error) {
|
||||||
|
return image.StoreInfo{FirstStagingImageTS: time.Time{}.Add(time.Second)}, nil
|
||||||
|
},
|
||||||
|
CommitFunc: func(id string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
ResetCleanupTimerFunc: func(id string) error { return nil },
|
||||||
|
}
|
||||||
imgSvc := image.NewService(&mockStore,
|
imgSvc := image.NewService(&mockStore,
|
||||||
image.ServiceParams{
|
image.ServiceParams{
|
||||||
EditDuration: 10 * time.Millisecond,
|
EditDuration: 10 * time.Millisecond,
|
||||||
@@ -1408,10 +1455,6 @@ func TestService_ResubmitStagingImages(t *testing.T) {
|
|||||||
defer teardown()
|
defer teardown()
|
||||||
b := DataStore{Engine: eng, EditDuration: 10 * time.Millisecond, ImageService: imgSvc}
|
b := DataStore{Engine: eng, EditDuration: 10 * time.Millisecond, ImageService: imgSvc}
|
||||||
|
|
||||||
mockStore.On("ResetCleanupTimer", "dev_user/bqf122eq9r8ad657n3ng").Once().Return(nil)
|
|
||||||
mockStore.On("ResetCleanupTimer", "dev_user/bqf321eq9r8ad657n3ng").Once().Return(nil)
|
|
||||||
mockStore.On("ResetCleanupTimer", "cached_images/12318fbd4c55e9d177b8b5ae197bc89c5afd8e07-a41fcb00643f28d700504256ec81cbf2e1aac53e").Once().Return(nil)
|
|
||||||
|
|
||||||
// create comment with three images without preparing it properly
|
// create comment with three images without preparing it properly
|
||||||
comment := store.Comment{
|
comment := store.Comment{
|
||||||
ID: "id-0",
|
ID: "id-0",
|
||||||
@@ -1427,21 +1470,19 @@ func TestService_ResubmitStagingImages(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// resubmit single comment with three images, of which two are in staging storage
|
// resubmit single comment with three images, of which two are in staging storage
|
||||||
mockStore.On("Info").Once().Return(image.StoreInfo{FirstStagingImageTS: time.Time{}.Add(time.Second)}, nil)
|
|
||||||
err = b.ResubmitStagingImages([]string{"radio-t"})
|
err = b.ResubmitStagingImages([]string{"radio-t"})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// wait for Submit goroutine to commit image
|
// wait for Submit goroutine to commit image
|
||||||
mockStore.On("Commit", "dev_user/bqf122eq9r8ad657n3ng").Once().Return(nil)
|
|
||||||
mockStore.On("Commit", "dev_user/bqf321eq9r8ad657n3ng").Once().Return(nil)
|
|
||||||
mockStore.On("Commit", "cached_images/12318fbd4c55e9d177b8b5ae197bc89c5afd8e07-a41fcb00643f28d700504256ec81cbf2e1aac53e").Once().Return(nil)
|
|
||||||
time.Sleep(b.EditDuration + time.Millisecond*100)
|
time.Sleep(b.EditDuration + time.Millisecond*100)
|
||||||
|
|
||||||
mockStore.AssertNumberOfCalls(t, "Info", 1)
|
assert.Equal(t, 1, len(mockStore.InfoCalls()))
|
||||||
mockStore.AssertNumberOfCalls(t, "Commit", 3)
|
assert.Equal(t, 3, len(mockStore.CommitCalls()))
|
||||||
|
|
||||||
// empty answer
|
// empty answer
|
||||||
mockStoreEmpty := image.MockStore{}
|
mockStoreEmpty := image.StoreMock{InfoFunc: func() (image.StoreInfo, error) {
|
||||||
|
return image.StoreInfo{FirstStagingImageTS: time.Time{}}, nil
|
||||||
|
}}
|
||||||
imgSvcEmpty := image.NewService(&mockStoreEmpty,
|
imgSvcEmpty := image.NewService(&mockStoreEmpty,
|
||||||
image.ServiceParams{
|
image.ServiceParams{
|
||||||
EditDuration: 10 * time.Millisecond,
|
EditDuration: 10 * time.Millisecond,
|
||||||
@@ -1450,15 +1491,16 @@ func TestService_ResubmitStagingImages(t *testing.T) {
|
|||||||
defer imgSvcEmpty.Close(context.TODO())
|
defer imgSvcEmpty.Close(context.TODO())
|
||||||
bEmpty := DataStore{Engine: eng, EditDuration: 10 * time.Millisecond, ImageService: imgSvcEmpty}
|
bEmpty := DataStore{Engine: eng, EditDuration: 10 * time.Millisecond, ImageService: imgSvcEmpty}
|
||||||
|
|
||||||
// resubmit receive empty timestamp and should do nothing
|
// resubmit receive empty timestamp and should do nothing )
|
||||||
mockStoreEmpty.On("Info").Once().Return(image.StoreInfo{FirstStagingImageTS: time.Time{}}, nil)
|
|
||||||
err = bEmpty.ResubmitStagingImages([]string{"radio-t", "non_existent"})
|
err = bEmpty.ResubmitStagingImages([]string{"radio-t", "non_existent"})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
mockStoreEmpty.AssertNumberOfCalls(t, "Info", 1)
|
assert.Equal(t, 1, len(mockStore.InfoCalls()))
|
||||||
|
|
||||||
// error from image storage
|
// error from image storage
|
||||||
mockStoreError := image.MockStore{}
|
mockStoreError := image.StoreMock{InfoFunc: func() (image.StoreInfo, error) {
|
||||||
|
return image.StoreInfo{}, fmt.Errorf("mock_err")
|
||||||
|
}}
|
||||||
imgSvcError := image.NewService(&mockStoreError,
|
imgSvcError := image.NewService(&mockStoreError,
|
||||||
image.ServiceParams{
|
image.ServiceParams{
|
||||||
EditDuration: 10 * time.Millisecond,
|
EditDuration: 10 * time.Millisecond,
|
||||||
@@ -1468,15 +1510,20 @@ func TestService_ResubmitStagingImages(t *testing.T) {
|
|||||||
bError := DataStore{Engine: eng, EditDuration: 10 * time.Millisecond, ImageService: imgSvcError}
|
bError := DataStore{Engine: eng, EditDuration: 10 * time.Millisecond, ImageService: imgSvcError}
|
||||||
|
|
||||||
// resubmit will receive error from image storage and should return it
|
// resubmit will receive error from image storage and should return it
|
||||||
mockStoreError.On("Info").Once().Return(image.StoreInfo{}, fmt.Errorf("mock_err"))
|
|
||||||
err = bError.ResubmitStagingImages([]string{"radio-t"})
|
err = bError.ResubmitStagingImages([]string{"radio-t"})
|
||||||
assert.EqualError(t, err, "mock_err")
|
assert.EqualError(t, err, "mock_err")
|
||||||
|
|
||||||
mockStoreError.AssertNumberOfCalls(t, "Info", 1)
|
assert.Equal(t, 1, len(mockStore.InfoCalls()))
|
||||||
|
assert.Equal(t, 3, len(mockStore.ResetCleanupTimerCalls()))
|
||||||
|
assert.Equal(t, "dev_user/bqf122eq9r8ad657n3ng", mockStore.ResetCleanupTimerCalls()[0].ID)
|
||||||
|
assert.Equal(t, "dev_user/bqf321eq9r8ad657n3ng", mockStore.ResetCleanupTimerCalls()[1].ID)
|
||||||
|
assert.Equal(t, "cached_images/12318fbd4c55e9d177b8b5ae197bc89c5afd8e07-a41fcb00643f28d700504256ec81cbf2e1aac53e", mockStore.ResetCleanupTimerCalls()[2].ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestService_ResubmitStagingImages_EngineError(t *testing.T) {
|
func TestService_ResubmitStagingImages_EngineError(t *testing.T) {
|
||||||
mockStore := image.MockStore{}
|
mockStore := image.StoreMock{InfoFunc: func() (image.StoreInfo, error) {
|
||||||
|
return image.StoreInfo{FirstStagingImageTS: time.Time{}.Add(time.Second)}, nil
|
||||||
|
}}
|
||||||
imgSvc := image.NewService(&mockStore,
|
imgSvc := image.NewService(&mockStore,
|
||||||
image.ServiceParams{
|
image.ServiceParams{
|
||||||
EditDuration: 10 * time.Millisecond,
|
EditDuration: 10 * time.Millisecond,
|
||||||
@@ -1484,26 +1531,37 @@ func TestService_ResubmitStagingImages_EngineError(t *testing.T) {
|
|||||||
})
|
})
|
||||||
defer imgSvc.Close(context.TODO())
|
defer imgSvc.Close(context.TODO())
|
||||||
|
|
||||||
engineMock := engine.MockInterface{}
|
first := true
|
||||||
|
engineMock := engine.InterfaceMock{
|
||||||
|
FindFunc: func(req engine.FindRequest) ([]store.Comment, error) {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("mockError")
|
||||||
|
},
|
||||||
|
}
|
||||||
site1Req := engine.FindRequest{Locator: store.Locator{SiteID: "site1", URL: ""}, Sort: "time", Since: time.Time{}.Add(time.Second)}
|
site1Req := engine.FindRequest{Locator: store.Locator{SiteID: "site1", URL: ""}, Sort: "time", Since: time.Time{}.Add(time.Second)}
|
||||||
site2Req := engine.FindRequest{Locator: store.Locator{SiteID: "site2", URL: ""}, Sort: "time", Since: time.Time{}.Add(time.Second)}
|
site2Req := engine.FindRequest{Locator: store.Locator{SiteID: "site2", URL: ""}, Sort: "time", Since: time.Time{}.Add(time.Second)}
|
||||||
engineMock.On("Find", site1Req).Return(nil, nil)
|
|
||||||
engineMock.On("Find", site2Req).Return(nil, fmt.Errorf("mockError"))
|
|
||||||
b := DataStore{Engine: &engineMock, EditDuration: 10 * time.Millisecond, ImageService: imgSvc}
|
b := DataStore{Engine: &engineMock, EditDuration: 10 * time.Millisecond, ImageService: imgSvc}
|
||||||
|
|
||||||
// One call without error and one with error
|
// One call without error and one with error
|
||||||
mockStore.On("Info").Once().Return(image.StoreInfo{FirstStagingImageTS: time.Time{}.Add(time.Second)}, nil)
|
|
||||||
err := b.ResubmitStagingImages([]string{"site1", "site2"})
|
err := b.ResubmitStagingImages([]string{"site1", "site2"})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "problem finding comments for site site2: mockError")
|
assert.Contains(t, err.Error(), "problem finding comments for site site2: mockError")
|
||||||
|
|
||||||
mockStore.AssertNumberOfCalls(t, "Info", 1)
|
assert.Equal(t, 1, len(mockStore.InfoCalls()))
|
||||||
|
assert.Equal(t, 2, len(engineMock.FindCalls()))
|
||||||
|
assert.Equal(t, site1Req, engineMock.FindCalls()[0].Req)
|
||||||
|
assert.Equal(t, site2Req, engineMock.FindCalls()[1].Req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestService_alterComment(t *testing.T) {
|
func TestService_alterComment(t *testing.T) {
|
||||||
engineMock := engine.MockInterface{}
|
engineMock := engine.InterfaceMock{
|
||||||
engineMock.On("Flag", engine.FlagRequest{Flag: engine.Blocked, UserID: "devid"}).Return(false, nil)
|
FlagFunc: func(req engine.FlagRequest) (bool, error) {
|
||||||
engineMock.On("Flag", engine.FlagRequest{Flag: engine.Verified, UserID: "devid"}).Return(false, nil)
|
return false, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
svc := DataStore{Engine: &engineMock}
|
svc := DataStore{Engine: &engineMock}
|
||||||
|
|
||||||
r := svc.alterComment(store.Comment{ID: "123", User: store.User{IP: "127.0.0.1", ID: "devid"},
|
r := svc.alterComment(store.Comment{ID: "123", User: store.User{IP: "127.0.0.1", ID: "devid"},
|
||||||
@@ -1514,24 +1572,48 @@ func TestService_alterComment(t *testing.T) {
|
|||||||
r = svc.alterComment(store.Comment{ID: "123", User: store.User{IP: "127.0.0.1", ID: "devid"}},
|
r = svc.alterComment(store.Comment{ID: "123", User: store.User{IP: "127.0.0.1", ID: "devid"}},
|
||||||
store.User{Name: "dev", ID: "devid", Admin: true})
|
store.User{Name: "dev", ID: "devid", Admin: true})
|
||||||
assert.Equal(t, store.Comment{ID: "123", User: store.User{IP: "127.0.0.1", ID: "devid"}}, r, "ip not cleaned")
|
assert.Equal(t, store.Comment{ID: "123", User: store.User{IP: "127.0.0.1", ID: "devid"}}, r, "ip not cleaned")
|
||||||
|
assert.Equal(t, 4, len(engineMock.FlagCalls()))
|
||||||
|
assert.Equal(t, engine.FlagRequest{Flag: engine.Blocked, UserID: "devid"}, engineMock.FlagCalls()[0].Req)
|
||||||
|
assert.Equal(t, engine.FlagRequest{Flag: engine.Verified, UserID: "devid"}, engineMock.FlagCalls()[1].Req)
|
||||||
|
assert.Equal(t, engine.FlagRequest{Flag: engine.Blocked, UserID: "devid"}, engineMock.FlagCalls()[2].Req)
|
||||||
|
assert.Equal(t, engine.FlagRequest{Flag: engine.Verified, UserID: "devid"}, engineMock.FlagCalls()[3].Req)
|
||||||
|
|
||||||
engineMock = engine.MockInterface{}
|
first := true
|
||||||
engineMock.On("Flag", engine.FlagRequest{Flag: engine.Blocked, UserID: "devid"}).Return(false, nil)
|
engineMock = engine.InterfaceMock{
|
||||||
engineMock.On("Flag", engine.FlagRequest{Flag: engine.Verified, UserID: "devid"}).Return(true, nil)
|
FlagFunc: func(req engine.FlagRequest) (bool, error) {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
svc = DataStore{Engine: &engineMock}
|
svc = DataStore{Engine: &engineMock}
|
||||||
r = svc.alterComment(store.Comment{ID: "123", User: store.User{IP: "127.0.0.1", ID: "devid", Verified: true}},
|
r = svc.alterComment(store.Comment{ID: "123", User: store.User{IP: "127.0.0.1", ID: "devid", Verified: true}},
|
||||||
store.User{Name: "dev", ID: "devid", Admin: false})
|
store.User{Name: "dev", ID: "devid", Admin: false})
|
||||||
assert.Equal(t, store.Comment{ID: "123", User: store.User{IP: "", ID: "devid", Verified: true}}, r, "verified set")
|
assert.Equal(t, store.Comment{ID: "123", User: store.User{IP: "", ID: "devid", Verified: true}}, r, "verified set")
|
||||||
|
assert.Equal(t, 2, len(engineMock.FlagCalls()))
|
||||||
|
assert.Equal(t, engine.FlagRequest{Flag: engine.Blocked, UserID: "devid"}, engineMock.FlagCalls()[0].Req)
|
||||||
|
assert.Equal(t, engine.FlagRequest{Flag: engine.Verified, UserID: "devid"}, engineMock.FlagCalls()[1].Req)
|
||||||
|
|
||||||
engineMock = engine.MockInterface{}
|
first = true
|
||||||
engineMock.On("Flag", engine.FlagRequest{Flag: engine.Blocked, UserID: "devid"}).Return(true, nil)
|
engineMock = engine.InterfaceMock{
|
||||||
engineMock.On("Flag", engine.FlagRequest{Flag: engine.Verified, UserID: "devid"}).Return(false, nil)
|
FlagFunc: func(req engine.FlagRequest) (bool, error) {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
svc = DataStore{Engine: &engineMock}
|
svc = DataStore{Engine: &engineMock}
|
||||||
r = svc.alterComment(store.Comment{ID: "123", User: store.User{IP: "127.0.0.1", ID: "devid", Verified: true},
|
r = svc.alterComment(store.Comment{ID: "123", User: store.User{IP: "127.0.0.1", ID: "devid", Verified: true},
|
||||||
Locator: store.Locator{URL: "javascript:alert('XSS1')"}},
|
Locator: store.Locator{URL: "javascript:alert('XSS1')"}},
|
||||||
store.User{Name: "dev", ID: "devid", Admin: false})
|
store.User{Name: "dev", ID: "devid", Admin: false})
|
||||||
assert.Equal(t, store.Comment{ID: "123", User: store.User{IP: "", Verified: true, Blocked: true, ID: "devid"},
|
assert.Equal(t, store.Comment{ID: "123", User: store.User{IP: "", Verified: true, Blocked: true, ID: "devid"},
|
||||||
Deleted: false}, r, "blocked")
|
Deleted: false}, r, "blocked")
|
||||||
|
assert.Equal(t, 1, len(engineMock.FlagCalls()))
|
||||||
|
assert.Equal(t, engine.FlagRequest{Flag: engine.Blocked, UserID: "devid"}, engineMock.FlagCalls()[0].Req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Benchmark_ServiceCreate(b *testing.B) {
|
func Benchmark_ServiceCreate(b *testing.B) {
|
||||||
@@ -1558,7 +1640,7 @@ func Benchmark_ServiceCreate(b *testing.B) {
|
|||||||
|
|
||||||
// makes new boltdb, put two records
|
// makes new boltdb, put two records
|
||||||
func prepStoreEngine(t *testing.T) (e engine.Interface, teardown func()) {
|
func prepStoreEngine(t *testing.T) (e engine.Interface, teardown func()) {
|
||||||
testDBLoc, err := ioutil.TempDir("", "test_image_r42")
|
testDBLoc, err := os.MkdirTemp("", "test_image_r42")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
testDB := path.Join(testDBLoc, "test.db")
|
testDB := path.Join(testDBLoc, "test.db")
|
||||||
_ = os.Remove(testDB)
|
_ = os.Remove(testDB)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
|
|
||||||
func TestMakeTree(t *testing.T) {
|
func TestMakeTree(t *testing.T) {
|
||||||
loc := store.Locator{URL: "url", SiteID: "site"}
|
loc := store.Locator{URL: "url", SiteID: "site"}
|
||||||
ts := func(min int, sec int) time.Time { return time.Date(2017, 12, 25, 19, min, sec, 0, time.UTC) }
|
ts := func(min, sec int) time.Time { return time.Date(2017, 12, 25, 19, min, sec, 0, time.UTC) }
|
||||||
|
|
||||||
// unsorted by purpose
|
// unsorted by purpose
|
||||||
comments := []store.Comment{
|
comments := []store.Comment{
|
||||||
@@ -54,7 +54,7 @@ func TestMakeTree(t *testing.T) {
|
|||||||
|
|
||||||
func TestMakeEmptySubtree(t *testing.T) {
|
func TestMakeEmptySubtree(t *testing.T) {
|
||||||
loc := store.Locator{URL: "url", SiteID: "site"}
|
loc := store.Locator{URL: "url", SiteID: "site"}
|
||||||
ts := func(min int, sec int) time.Time { return time.Date(2017, 12, 25, 19, min, sec, 0, time.UTC) }
|
ts := func(min, sec int) time.Time { return time.Date(2017, 12, 25, 19, min, sec, 0, time.UTC) }
|
||||||
|
|
||||||
// unsorted by purpose
|
// unsorted by purpose
|
||||||
comments := []store.Comment{
|
comments := []store.Comment{
|
||||||
|
|||||||
@@ -1,38 +1,24 @@
|
|||||||
package templates
|
package templates
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"embed"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
log "github.com/go-pkgz/lgr"
|
|
||||||
"github.com/rakyll/statik/fs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// FS stores link to statikFS if it exists
|
//go:embed static
|
||||||
type FS struct {
|
var templateFS embed.FS
|
||||||
statik http.FileSystem
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileReader describes methods of filesystem
|
// Read reads either template from disk if it exists, or from embedded template
|
||||||
type FileReader interface {
|
func Read(path string) ([]byte, error) {
|
||||||
ReadFile(path string) ([]byte, error)
|
if _, err := os.Stat(filepath.Clean(path)); err == nil {
|
||||||
}
|
return os.ReadFile(filepath.Clean(path))
|
||||||
|
|
||||||
// NewFS returns new FS instance, which will read from statik if it's available and from fs otherwise
|
|
||||||
func NewFS() *FS {
|
|
||||||
f := &FS{}
|
|
||||||
if statikFS, err := fs.NewWithNamespace("templates"); err == nil {
|
|
||||||
log.Printf("[INFO] templates will be read from statik")
|
|
||||||
f.statik = statikFS
|
|
||||||
}
|
}
|
||||||
return f
|
// remove "static/" prefix from path
|
||||||
}
|
var contentFS, err = fs.Sub(templateFS, "static")
|
||||||
|
if err != nil {
|
||||||
// ReadFile depends on statik achieve exists
|
return nil, err
|
||||||
func (f *FS) ReadFile(path string) ([]byte, error) {
|
|
||||||
if f.statik != nil {
|
|
||||||
return fs.ReadFile(f.statik, filepath.Join("/", path)) //nolint:gocritic // root folder is a requirement for statik
|
|
||||||
}
|
}
|
||||||
return os.ReadFile(filepath.Clean(path))
|
return fs.ReadFile(contentFS, filepath.Clean(path))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,19 +6,12 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewFS(t *testing.T) {
|
func Test_Read(t *testing.T) {
|
||||||
fs := NewFS()
|
file, err := Read("testdata/template.html.tmpl")
|
||||||
assert.NotNil(t, &fs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFS_ReadFile(t *testing.T) {
|
|
||||||
fs := NewFS()
|
|
||||||
|
|
||||||
file, err := fs.ReadFile("testdata/template.html.tmpl")
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, []byte("template\n"), file)
|
assert.Equal(t, []byte("template\n"), file)
|
||||||
|
|
||||||
file, err = fs.ReadFile("testdata/bad_path.html.tmpl")
|
file, err = Read("testdata/bad_path.html.tmpl")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Nil(t, file)
|
assert.Nil(t, file)
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-32
@@ -3,77 +3,76 @@ module github.com/umputun/remark42/backend
|
|||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Depado/bfchroma v1.3.0
|
github.com/Depado/bfchroma/v2 v2.0.0
|
||||||
github.com/PuerkitoBio/goquery v1.8.0
|
github.com/PuerkitoBio/goquery v1.8.0
|
||||||
github.com/alecthomas/chroma v0.10.0
|
github.com/alecthomas/chroma/v2 v2.3.0
|
||||||
github.com/didip/tollbooth/v6 v6.1.2
|
github.com/didip/tollbooth/v7 v7.0.1
|
||||||
github.com/didip/tollbooth_chi v0.0.0-20220429013743-da966f2f674b
|
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f
|
||||||
github.com/go-chi/chi/v5 v5.0.7
|
github.com/go-chi/chi/v5 v5.0.7
|
||||||
github.com/go-chi/cors v1.2.1
|
github.com/go-chi/cors v1.2.1
|
||||||
github.com/go-chi/render v1.0.1
|
github.com/go-chi/render v1.0.2
|
||||||
github.com/go-pkgz/auth v1.19.0
|
github.com/go-pkgz/auth v1.20.0
|
||||||
github.com/go-pkgz/jrpc v0.2.0
|
github.com/go-pkgz/jrpc v0.3.0
|
||||||
github.com/go-pkgz/lcw v0.8.1
|
github.com/go-pkgz/lcw v1.0.1
|
||||||
github.com/go-pkgz/lgr v0.10.4
|
github.com/go-pkgz/lgr v0.10.4
|
||||||
github.com/go-pkgz/notify v0.1.1
|
github.com/go-pkgz/notify v0.2.0
|
||||||
github.com/go-pkgz/repeater v1.1.3
|
github.com/go-pkgz/repeater v1.1.3
|
||||||
github.com/go-pkgz/rest v1.15.0
|
github.com/go-pkgz/rest v1.16.0
|
||||||
github.com/go-pkgz/syncs v1.2.0
|
github.com/go-pkgz/syncs v1.2.0
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/gorilla/feeds v1.1.1
|
github.com/gorilla/feeds v1.1.1
|
||||||
github.com/hashicorp/go-multierror v1.1.1
|
github.com/hashicorp/go-multierror v1.1.1
|
||||||
github.com/jessevdk/go-flags v1.5.0
|
github.com/jessevdk/go-flags v1.5.0
|
||||||
github.com/kyokomi/emoji/v2 v2.2.9
|
github.com/kyokomi/emoji/v2 v2.2.10
|
||||||
github.com/microcosm-cc/bluemonday v1.0.18
|
github.com/microcosm-cc/bluemonday v1.0.20
|
||||||
github.com/rakyll/statik v0.1.7
|
|
||||||
github.com/rs/xid v1.4.0
|
github.com/rs/xid v1.4.0
|
||||||
github.com/russross/blackfriday/v2 v2.1.0
|
github.com/russross/blackfriday/v2 v2.1.0
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
github.com/stretchr/testify v1.7.1
|
github.com/stretchr/testify v1.8.0
|
||||||
go.etcd.io/bbolt v1.3.6
|
go.etcd.io/bbolt v1.3.6
|
||||||
go.uber.org/goleak v1.1.12
|
go.uber.org/goleak v1.2.0
|
||||||
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
||||||
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9
|
golang.org/x/image v0.0.0-20220617043117-41969df76e82
|
||||||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2
|
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/compute v1.6.0 // indirect
|
cloud.google.com/go/compute v1.6.1 // indirect
|
||||||
|
github.com/ajg/form v1.5.1 // indirect
|
||||||
github.com/andybalholm/cascadia v1.3.1 // indirect
|
github.com/andybalholm/cascadia v1.3.1 // indirect
|
||||||
github.com/aymerick/douceur v0.2.0 // indirect
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dghubble/oauth1 v0.7.1 // indirect
|
github.com/dghubble/oauth1 v0.7.1 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||||
github.com/go-chi/chi v4.1.1+incompatible // indirect
|
github.com/go-oauth2/oauth2/v4 v4.5.1 // indirect
|
||||||
github.com/go-oauth2/oauth2/v4 v4.4.3 // indirect
|
github.com/go-pkgz/email v0.4.0 // indirect
|
||||||
github.com/go-pkgz/email v0.3.0 // indirect
|
github.com/go-pkgz/expirable-cache v0.1.0 // indirect
|
||||||
github.com/go-pkgz/expirable-cache v0.0.3 // indirect
|
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||||
github.com/go-redis/redis/v7 v7.4.1 // indirect
|
|
||||||
github.com/go-stack/stack v1.8.1 // indirect
|
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/gorilla/css v1.0.0 // indirect
|
github.com/gorilla/css v1.0.0 // indirect
|
||||||
github.com/gorilla/websocket v1.5.0 // indirect
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||||
github.com/klauspost/compress v1.15.1 // indirect
|
github.com/klauspost/compress v1.15.2 // indirect
|
||||||
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
||||||
github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022 // indirect
|
github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/slack-go/slack v0.10.3 // indirect
|
github.com/slack-go/slack v0.11.3 // indirect
|
||||||
github.com/stretchr/objx v0.3.0 // indirect
|
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
github.com/xdg-go/scram v1.1.1 // indirect
|
github.com/xdg-go/scram v1.1.1 // indirect
|
||||||
github.com/xdg-go/stringprep v1.0.3 // indirect
|
github.com/xdg-go/stringprep v1.0.3 // indirect
|
||||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
|
||||||
go.mongodb.org/mongo-driver v1.9.1 // indirect
|
go.mongodb.org/mongo-driver v1.10.2 // indirect
|
||||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
|
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
|
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
|
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/protobuf v1.28.0 // indirect
|
google.golang.org/protobuf v1.28.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
+103
-124
@@ -35,11 +35,11 @@ cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUM
|
|||||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||||
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
|
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
|
||||||
cloud.google.com/go/compute v1.2.0/go.mod h1:xlogom/6gr8RJGBe7nT2eGsQYAFUbbv8dbC29qE3Xmw=
|
|
||||||
cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
|
cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
|
||||||
cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
|
cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
|
||||||
cloud.google.com/go/compute v1.6.0 h1:XdQIN5mdPTSBVwSIVDuY5e8ZzVAccsHvD3qTEz4zIps=
|
|
||||||
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
|
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
|
||||||
|
cloud.google.com/go/compute v1.6.1 h1:2sMmt8prCn7DPaG4Pmh0N3Inmc8cT8ae5k1M6VJ9Wqc=
|
||||||
|
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
@@ -54,35 +54,36 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
|
|||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/Depado/bfchroma v1.3.0 h1:zz14vpvySU6S0CL6yGPr1vkFevQecIt8dJdCsMS2JpM=
|
github.com/Depado/bfchroma/v2 v2.0.0 h1:IRpN9BPkNwEpR6w1ectIcNWOuhDSLx+8f1pn83fzxx8=
|
||||||
github.com/Depado/bfchroma v1.3.0/go.mod h1:c0bFk0tFmT+clD3TIGurjWCfD/QV8/EebfM3JGr+98M=
|
github.com/Depado/bfchroma/v2 v2.0.0/go.mod h1:wFwW/Pw8Tnd0irzgO9Zxtxgzp3aPS8qBWlyadxujxmw=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
|
github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
|
||||||
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
|
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
|
||||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||||
github.com/alecthomas/chroma v0.7.3/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
github.com/alecthomas/chroma/v2 v2.3.0 h1:83xfxrnjv8eK+Cf8qZDzNo3PPF9IbTWHs7z28GY6D0U=
|
||||||
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
|
github.com/alecthomas/chroma/v2 v2.3.0/go.mod h1:mZxeWZlxP2Dy+/8cBob2PYd8O2DwNAzave5AY7A2eQw=
|
||||||
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
|
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
|
||||||
github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
|
github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
||||||
github.com/alecthomas/repr v0.0.0-20200325044227-4184120f674c/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||||
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 h1:45bxf7AZMwWcqkLzDAQugVEwedisr5nRJ1r+7LYnv0U=
|
github.com/alicebob/miniredis/v2 v2.22.0 h1:lIHHiSkEyS1MkKHCHzN+0mWrA4YdbGdimE5iZ2sHSzo=
|
||||||
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
github.com/alicebob/miniredis/v2 v2.22.0/go.mod h1:XNqvJdQJv5mSuVMc0ynneafpnL/zv52acZ6kqeS0t88=
|
||||||
github.com/alicebob/miniredis/v2 v2.11.4 h1:GsuyeunTx7EllZBU3/6Ji3dhMQZDpC9rLf1luJ+6M5M=
|
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||||
github.com/alicebob/miniredis/v2 v2.11.4/go.mod h1:VL3UDEfAH59bSa7MuHMuFToxkqyHh69s/WUbYlOAuyg=
|
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4=
|
|
||||||
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
|
||||||
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
|
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
|
||||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
@@ -95,19 +96,18 @@ github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWH
|
|||||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dghubble/oauth1 v0.7.1 h1:JjbOVSVVkms9A4h/sTQy5Jb2nFuAAVb2qVYgenJPyrE=
|
github.com/dghubble/oauth1 v0.7.1 h1:JjbOVSVVkms9A4h/sTQy5Jb2nFuAAVb2qVYgenJPyrE=
|
||||||
github.com/dghubble/oauth1 v0.7.1/go.mod h1:0eEzON0UY/OLACQrmnjgJjmvCGXzjBCsZqL1kWDXtF0=
|
github.com/dghubble/oauth1 v0.7.1/go.mod h1:0eEzON0UY/OLACQrmnjgJjmvCGXzjBCsZqL1kWDXtF0=
|
||||||
github.com/didip/tollbooth/v6 v6.0.1/go.mod h1:j2pKs+JQ5PvU/K4jFnrnwntrmfUbYLJE5oSdxR37FD0=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
github.com/didip/tollbooth/v6 v6.1.2 h1:Kdqxmqw9YTv0uKajBUiWQg+GURL/k4vy9gmLCL01PjQ=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/didip/tollbooth/v6 v6.1.2/go.mod h1:xjcse6CTHCLuOkzsWrEgdy9WPJFv+p/x6v+MyfP+O9s=
|
github.com/didip/tollbooth/v7 v7.0.0/go.mod h1:VZhDSGl5bDSPj4wPsih3PFa4Uh9Ghv8hgacaTm5PRT4=
|
||||||
github.com/didip/tollbooth_chi v0.0.0-20200524181329-8b84cd7183d9/go.mod h1:YWyIfq3y4ArRfWZ9XksmuusP+7Mad+T0iFZ0kv0XG/M=
|
github.com/didip/tollbooth/v7 v7.0.1 h1:TkT4sBKoQoHQFPf7blQ54iHrZiTDnr8TceU+MulVAog=
|
||||||
github.com/didip/tollbooth_chi v0.0.0-20220429013743-da966f2f674b h1:elkngQhLBcyDIXwL9Z7AfXXbBszUEsLxqNw6WPF8Mtc=
|
github.com/didip/tollbooth/v7 v7.0.1/go.mod h1:VZhDSGl5bDSPj4wPsih3PFa4Uh9Ghv8hgacaTm5PRT4=
|
||||||
github.com/didip/tollbooth_chi v0.0.0-20220429013743-da966f2f674b/go.mod h1:0ZVa6kSzS011nfTC1rELyxK4tjVf6vqBnOv7oY2KlsA=
|
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f h1:jtKwihcLmUC9BAhoJ9adCUqdSSZcOdH2KL7mPTUm2aw=
|
||||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f/go.mod h1:q9C80dnsuVRP2dAskjnXRNWdUJqtGgwG9wNrzt0019s=
|
||||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
@@ -128,48 +128,44 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
|
|||||||
github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8=
|
github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8=
|
||||||
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
|
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/go-chi/chi v4.1.1+incompatible h1:MmTgB0R8Bt/jccxp+t6S/1VGIKdJw5J74CK/c9tTfA4=
|
|
||||||
github.com/go-chi/chi v4.1.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
|
||||||
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
|
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
|
||||||
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||||
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
|
|
||||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||||
|
github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
|
||||||
|
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-oauth2/oauth2/v4 v4.4.3 h1:IiU3iAK5NEeZ4mUf5HV9tN7iNlx4AGUkIJ5M1C4ARxY=
|
github.com/go-oauth2/oauth2/v4 v4.5.1 h1:3vxp+cjLqDe1TbogbwtMyeHRHr1tD+ksrK7xNppYRDs=
|
||||||
github.com/go-oauth2/oauth2/v4 v4.4.3/go.mod h1:NR9Hugz5/Qe2OGxoPBhsTRNjnm/amC+z9+XTwt63rhs=
|
github.com/go-oauth2/oauth2/v4 v4.5.1/go.mod h1:wk/2uLImWIa9VVQDgxz99H2GDbhmfi/9/Xr+GvkSUSQ=
|
||||||
github.com/go-pkgz/auth v1.19.0 h1:TTfbQvlqsuEhRpYAiY/uF5UGP+HWMc5CN2RAhN06ik0=
|
github.com/go-pkgz/auth v1.20.0 h1:9SHkolgv9+zOI9U+8IFXra0pl/H90oE4+ciJWtXoDl4=
|
||||||
github.com/go-pkgz/auth v1.19.0/go.mod h1:1pu95rx7tFfeIFq0pjQPwCeL6qGYH4+UxHJudhK0T7M=
|
github.com/go-pkgz/auth v1.20.0/go.mod h1:jz0djN+4XoCyieuOzc+rB9l0RdqeuiXoQfE02yonLAI=
|
||||||
github.com/go-pkgz/email v0.3.0 h1:89FbVF6trHkdn2VVoXlBfZHkdYQ1vLmQ54OhdosaFtc=
|
github.com/go-pkgz/email v0.3.1-0.20221002173339-19d25a20d99c/go.mod h1:TpnmSLkQW3FyICit2hn7WIhCUDrhCX6btzz5wS3wHRI=
|
||||||
github.com/go-pkgz/email v0.3.0/go.mod h1:TpnmSLkQW3FyICit2hn7WIhCUDrhCX6btzz5wS3wHRI=
|
github.com/go-pkgz/email v0.4.0 h1:NiYCwKPR6sW8nJsOR3GyB9Yw1AQZdnY78Xnhd9SLHEs=
|
||||||
github.com/go-pkgz/expirable-cache v0.0.3 h1:rTh6qNPp78z0bQE6HDhXBHUwqnV9i09Vm6dksJLXQDc=
|
github.com/go-pkgz/email v0.4.0/go.mod h1:TpnmSLkQW3FyICit2hn7WIhCUDrhCX6btzz5wS3wHRI=
|
||||||
github.com/go-pkgz/expirable-cache v0.0.3/go.mod h1:+IauqN00R2FqNRLCLA+X5YljQJrwB179PfiAoMPlTlQ=
|
github.com/go-pkgz/expirable-cache v0.1.0 h1:3bw0m8vlTK8qlwz5KXuygNBTkiKRTPrAGXU0Ej2AC1g=
|
||||||
github.com/go-pkgz/jrpc v0.2.0 h1:CLy/eZyekjraVrxZV18N2R1mYLMJ/nWrgdfyIOGPY/E=
|
github.com/go-pkgz/expirable-cache v0.1.0/go.mod h1:GTrEl0X+q0mPNqN6dtcQXksACnzCBQ5k/k1SwXJsZKs=
|
||||||
github.com/go-pkgz/jrpc v0.2.0/go.mod h1:wd8vtQ4CgtCnuqua6x2b1SKIgv0VSOh5Dn0uUITbiUE=
|
github.com/go-pkgz/jrpc v0.3.0 h1:Fls38KqPsHzvp0FWfivr6cGnncC+iFBodHBqvUPY+0U=
|
||||||
github.com/go-pkgz/lcw v0.8.1 h1:Bpt2yYTE1J8hIhz8tjdm1WPOgH13eo5iTNsXyop7cMQ=
|
github.com/go-pkgz/jrpc v0.3.0/go.mod h1:MFtKs75JESiSqVicsQkgN2iDFFuCd3gVT1/vKiwRi00=
|
||||||
github.com/go-pkgz/lcw v0.8.1/go.mod h1:Xw0/ZfApATgbjVPYRZO4XHdWyxAjErDWDWJ7TLlw1Vc=
|
github.com/go-pkgz/lcw v1.0.1 h1:svYC6LIyzRaHF3TwJ8GCS+2RkJreBfaFjoeS+UYwJBc=
|
||||||
|
github.com/go-pkgz/lcw v1.0.1/go.mod h1:CPJJzunpmGToOtD0Ga82TV152eL69sYEIIPcy9fbxlU=
|
||||||
github.com/go-pkgz/lgr v0.10.4 h1:l7qyFjqEZgwRgaQQSEp6tve4A3OU80VrfzpvtEX8ngw=
|
github.com/go-pkgz/lgr v0.10.4 h1:l7qyFjqEZgwRgaQQSEp6tve4A3OU80VrfzpvtEX8ngw=
|
||||||
github.com/go-pkgz/lgr v0.10.4/go.mod h1:CD0s1z6EFpIUplV067gitF77tn25JItzwHNKAPqeCF0=
|
github.com/go-pkgz/lgr v0.10.4/go.mod h1:CD0s1z6EFpIUplV067gitF77tn25JItzwHNKAPqeCF0=
|
||||||
github.com/go-pkgz/notify v0.1.1 h1:0EaPnBD/IX8EBVAmKnEHNesyH3rD1v6w0MBLRCwrTUI=
|
github.com/go-pkgz/notify v0.2.0 h1:mxHjcLc3goT+k1qnBPJ06PpNuVUDcu21Xy+6hEo4IaU=
|
||||||
github.com/go-pkgz/notify v0.1.1/go.mod h1:XTzsXgwXXsXbl8iz6qTsCq6gLkaOkimL8mJfLrk3bCk=
|
github.com/go-pkgz/notify v0.2.0/go.mod h1:uB4bcohkZB+P8zT279KQv5aXqQEkeMXI3Ey0Y3r0SQk=
|
||||||
github.com/go-pkgz/repeater v1.1.3 h1:q6+JQF14ESSy28Dd7F+wRelY4F+41HJ0LEy/szNnMiE=
|
github.com/go-pkgz/repeater v1.1.3 h1:q6+JQF14ESSy28Dd7F+wRelY4F+41HJ0LEy/szNnMiE=
|
||||||
github.com/go-pkgz/repeater v1.1.3/go.mod h1:hVTavuO5x3Gxnu8zW7d6sQBfAneKV8X2FjU48kGfpKw=
|
github.com/go-pkgz/repeater v1.1.3/go.mod h1:hVTavuO5x3Gxnu8zW7d6sQBfAneKV8X2FjU48kGfpKw=
|
||||||
github.com/go-pkgz/rest v1.5.0/go.mod h1:nQaM3RhSTUAmbBZWY4hfe4buyeC9VckvhoCktiQXJxI=
|
github.com/go-pkgz/rest v1.15.6/go.mod h1:KUWAqbDteYGS/CiXftomQsKjtEOifXsJ36Ka0skYbmk=
|
||||||
github.com/go-pkgz/rest v1.12.2/go.mod h1:KUWAqbDteYGS/CiXftomQsKjtEOifXsJ36Ka0skYbmk=
|
github.com/go-pkgz/rest v1.16.0 h1:3An51XJXUq1XtICHvfAEp048ZCbUHBEga3g0WEu3zK0=
|
||||||
github.com/go-pkgz/rest v1.15.0 h1:v/BDqJF9robo85GME85GWJ7O/NjtAO0x7LvO4EqWNRE=
|
github.com/go-pkgz/rest v1.16.0/go.mod h1:HHlLOt02NJc2sgffXBF6hYVMcRo4Gz3vjg43zTzN7VM=
|
||||||
github.com/go-pkgz/rest v1.15.0/go.mod h1:KUWAqbDteYGS/CiXftomQsKjtEOifXsJ36Ka0skYbmk=
|
|
||||||
github.com/go-pkgz/syncs v1.2.0 h1:aiizQFILlMZ4KtRNaYLcDffRbUQZH9fclsgr5KybWyY=
|
github.com/go-pkgz/syncs v1.2.0 h1:aiizQFILlMZ4KtRNaYLcDffRbUQZH9fclsgr5KybWyY=
|
||||||
github.com/go-pkgz/syncs v1.2.0/go.mod h1:fjThZdM2FkC/oSeiqBTOZOtHpbrCh4HuHbipB5qZJJM=
|
github.com/go-pkgz/syncs v1.2.0/go.mod h1:fjThZdM2FkC/oSeiqBTOZOtHpbrCh4HuHbipB5qZJJM=
|
||||||
github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
|
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||||
github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOrTI=
|
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||||
github.com/go-redis/redis/v7 v7.4.1/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
|
|
||||||
github.com/go-session/session v3.1.2+incompatible/go.mod h1:8B3iivBQjrz/JtC68Np2T1yBBLxTan3mn/3OM0CyRt0=
|
github.com/go-session/session v3.1.2+incompatible/go.mod h1:8B3iivBQjrz/JtC68Np2T1yBBLxTan3mn/3OM0CyRt0=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
|
|
||||||
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
|
|
||||||
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
|
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-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||||
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||||
@@ -210,7 +206,6 @@ github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
|
|||||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
@@ -244,6 +239,7 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
|
|||||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
@@ -270,7 +266,6 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb
|
|||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
@@ -289,78 +284,74 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
|
|||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.10.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
|
||||||
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
|
||||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
github.com/klauspost/compress v1.15.1 h1:y9FcTHGyrebwfP0ZZqFiaxTaiDnUrGkJkI+f583BL1A=
|
github.com/klauspost/compress v1.15.2 h1:3WH+AG7s2+T8o3nrM/8u2rdqUEcQhmga7smjrT41nAw=
|
||||||
github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
github.com/klauspost/compress v1.15.2/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kyokomi/emoji/v2 v2.2.9 h1:UWYkjplPZ4rMPvLxc+/e12/xTqoRcn55oUySkpZ554g=
|
github.com/kyokomi/emoji/v2 v2.2.10 h1:1z5eMVcxFifsmEoNpdeq4UahbcicgQ4FEHuzrCVwmiI=
|
||||||
github.com/kyokomi/emoji/v2 v2.2.9/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE=
|
github.com/kyokomi/emoji/v2 v2.2.10/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE=
|
||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
|
||||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.18 h1:6HcxvXDAi3ARt3slx6nTesbvorIc3QeTzBNRvWktHBo=
|
github.com/microcosm-cc/bluemonday v1.0.20 h1:flpzsq4KU3QIYAYGV/szUat7H+GPOXR0B2JU5A1Wp8Y=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.18/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
|
github.com/microcosm-cc/bluemonday v1.0.20/go.mod h1:yfBmMi8mxvaZut3Yytv+jTXRY8mxyjJ0/kQBTElld50=
|
||||||
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
|
||||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||||
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
|
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
|
||||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||||
github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022 h1:Ys0rDzh8s4UMlGaDa1UTA0sfKgvF0hQZzTYX8ktjiDc=
|
github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022 h1:Ys0rDzh8s4UMlGaDa1UTA0sfKgvF0hQZzTYX8ktjiDc=
|
||||||
github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022/go.mod h1:x4NsS+uc7ecH/Cbm9xKQ6XzmJM57rWTkjywjfB2yQ18=
|
github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022/go.mod h1:x4NsS+uc7ecH/Cbm9xKQ6XzmJM57rWTkjywjfB2yQ18=
|
||||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||||
|
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
github.com/onsi/ginkgo v1.13.0 h1:M76yO2HkZASFjXL0HSoZJ1AYEmQxNJmY41Jx1zNUq1Y=
|
|
||||||
github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
|
github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
|
||||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||||
|
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||||
|
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
|
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
||||||
|
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ=
|
|
||||||
github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc=
|
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
|
github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
|
||||||
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
|
||||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||||
github.com/slack-go/slack v0.10.3 h1:kKYwlKY73AfSrtAk9UHWCXXfitudkDztNI9GYBviLxw=
|
github.com/slack-go/slack v0.11.3 h1:GN7revxEMax4amCc3El9a+9SGnjmBvSUobs0QnO6ZO8=
|
||||||
github.com/slack-go/slack v0.10.3/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
|
github.com/slack-go/slack v0.11.3/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E=
|
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E=
|
||||||
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
|
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
|
||||||
github.com/tidwall/buntdb v1.1.2 h1:noCrqQXL9EKMtcdwJcmuVKSEjqu1ua99RHHgbLTEHRo=
|
github.com/tidwall/buntdb v1.1.2 h1:noCrqQXL9EKMtcdwJcmuVKSEjqu1ua99RHHgbLTEHRo=
|
||||||
@@ -382,16 +373,13 @@ github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 h1:Otn9S136ELckZ
|
|||||||
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ=
|
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
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/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasthttp v1.14.0 h1:67bfuW9azCMwW/Jlq/C+VeihNpAuJMWkYPBig1gdi3A=
|
github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4=
|
||||||
github.com/valyala/fasthttp v1.14.0/go.mod h1:ol1PCaL0dX20wC0htZ7sYCsvCYmrouYra0zHzaclZhE=
|
github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0=
|
||||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||||
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
|
|
||||||
github.com/xdg-go/scram v1.1.0/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
|
|
||||||
github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E=
|
github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E=
|
||||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||||
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
|
||||||
github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs=
|
github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs=
|
||||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||||
@@ -415,13 +403,12 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
|
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw=
|
||||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
|
||||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||||
go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
|
go.mongodb.org/mongo-driver v1.10.2 h1:4Wk3cnqOrQCn0P92L3/mmurMxzdvWWs5J9jinAVKD+k=
|
||||||
go.mongodb.org/mongo-driver v1.9.1 h1:m078y9v7sBItkt1aaoe2YlvWEXcD263e1a4E1fBrJ1c=
|
go.mongodb.org/mongo-driver v1.10.2/go.mod h1:z4XpeoU6w+9Vht+jAFyLgVrD+jGSQQe0+CBWFHNiHt8=
|
||||||
go.mongodb.org/mongo-driver v1.9.1/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
|
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
@@ -430,18 +417,17 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|||||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||||
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
|
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||||
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
||||||
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 h1:SLP7Q4Di66FONjDJbCYrCRrh97focO6sLogHO7/g8F0=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@@ -454,9 +440,9 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
|
|||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
|
||||||
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 h1:LRtI4W37N+KFebI/qV0OFiLUv4GLOWeEW5hn/KEJvxE=
|
|
||||||
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||||
|
golang.org/x/image v0.0.0-20220617043117-41969df76e82 h1:KpZB5pUSBvrHltNEdK/tw0xlPeD13M6M6aGP32gKqiw=
|
||||||
|
golang.org/x/image v0.0.0-20220617043117-41969df76e82/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
@@ -494,7 +480,6 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
|
|||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
@@ -518,15 +503,16 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y=
|
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY=
|
||||||
|
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@@ -573,8 +559,6 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -587,13 +571,11 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -601,6 +583,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -620,15 +603,13 @@ golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
|
|
||||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
|
||||||
|
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@@ -644,9 +625,6 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
|
|
||||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306/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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
@@ -657,7 +635,6 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
|
|||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
@@ -693,6 +670,7 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u
|
|||||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
@@ -704,8 +682,9 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U=
|
||||||
|
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
@@ -737,11 +716,11 @@ google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqiv
|
|||||||
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
|
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
|
||||||
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
|
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
|
||||||
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
|
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
|
||||||
google.golang.org/api v0.66.0/go.mod h1:I1dmXYpX7HGwz/ejRxwQp2qj5bFAz93HiCU1C1oYd9M=
|
|
||||||
google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
|
google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
|
||||||
google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
|
google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
|
||||||
google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
|
google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
|
||||||
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
|
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
|
||||||
|
google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
@@ -810,9 +789,7 @@ google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ6
|
|||||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
google.golang.org/genproto v0.0.0-20220201184016-50beb8ab5c44/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||||
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||||
@@ -821,6 +798,8 @@ google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2
|
|||||||
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
|
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
|
||||||
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||||
google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||||
|
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||||
|
google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
@@ -873,16 +852,16 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|||||||
-16
@@ -1,16 +0,0 @@
|
|||||||
run:
|
|
||||||
timeout: 5m
|
|
||||||
|
|
||||||
linters:
|
|
||||||
enable:
|
|
||||||
- megacheck
|
|
||||||
- govet
|
|
||||||
disable:
|
|
||||||
- errcheck
|
|
||||||
- maligned
|
|
||||||
- prealloc
|
|
||||||
disable-all: false
|
|
||||||
presets:
|
|
||||||
- bugs
|
|
||||||
- unused
|
|
||||||
fast: false
|
|
||||||
Generated
Vendored
+10
-9
@@ -5,7 +5,7 @@ type: docker
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: test
|
- name: test
|
||||||
image: golang:1.13.1
|
image: golang:latest
|
||||||
volumes:
|
volumes:
|
||||||
- name: deps
|
- name: deps
|
||||||
path: /go
|
path: /go
|
||||||
@@ -13,13 +13,14 @@ steps:
|
|||||||
- go test -race -coverprofile=coverage.txt -covermode=atomic
|
- go test -race -coverprofile=coverage.txt -covermode=atomic
|
||||||
|
|
||||||
- name: linter
|
- name: linter
|
||||||
image: golang:1.13.1
|
image: golang:latest
|
||||||
volumes:
|
volumes:
|
||||||
- name: deps
|
- name: deps
|
||||||
path: /go
|
path: /go
|
||||||
commands:
|
commands:
|
||||||
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s v1.20.0
|
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s
|
||||||
- ./bin/golangci-lint run
|
- ./bin/golangci-lint run --timeout 5m
|
||||||
|
- rm -r ./bin/
|
||||||
|
|
||||||
- name: coverage
|
- name: coverage
|
||||||
image: plugins/codecov
|
image: plugins/codecov
|
||||||
@@ -37,8 +38,8 @@ steps:
|
|||||||
token:
|
token:
|
||||||
from_secret: telegram_token
|
from_secret: telegram_token
|
||||||
message: >
|
message: >
|
||||||
*{{repo.name}}*
|
*{{repo.name}}*
|
||||||
[Build {{build.number}}]({{build.link}}) by {{commit.author}} {{#success build.status}}succeeded{{else}}failed{{/success}} in {{buildtime build.started}}
|
[Build {{build.number}}]({{build.link}}) by {{commit.author}} {{#success build.status}}succeeded{{else}}failed{{/success}} in {{buildtime build.started}}
|
||||||
`{{truncate commit.sha 8}}`: "{{commit.message}}"
|
`{{truncate commit.sha 8}}`: "{{commit.message}}"
|
||||||
when:
|
when:
|
||||||
status:
|
status:
|
||||||
@@ -48,4 +49,4 @@ steps:
|
|||||||
volumes:
|
volumes:
|
||||||
- name: deps
|
- name: deps
|
||||||
host:
|
host:
|
||||||
path: /var/lib/cache/godeps/
|
path: /var/lib/cache/godeps/
|
||||||
Generated
Vendored
Generated
Vendored
+2
-5
@@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
[](https://forthebadge.com)[](https://forthebadge.com)
|
[](https://forthebadge.com)[](https://forthebadge.com)
|
||||||
|
|
||||||

|
|
||||||
[](https://goreportcard.com/report/github.com/Depado/bfchroma)
|
[](https://goreportcard.com/report/github.com/Depado/bfchroma)
|
||||||
[](https://drone.depado.eu/Depado/bfchroma)
|
[](https://drone.depa.do/Depado/bfchroma)
|
||||||
[](https://codecov.io/gh/Depado/bfchroma)
|
[](https://codecov.io/gh/Depado/bfchroma)
|
||||||
[](https://github.com/Depado/bfchroma/blob/master/LICENSE)
|
[](https://github.com/Depado/bfchroma/blob/master/LICENSE)
|
||||||
[](https://godoc.org/github.com/Depado/bfchroma)
|
[](https://godoc.org/github.com/Depado/bfchroma)
|
||||||
@@ -21,11 +20,9 @@ This project requires and uses the `v2` version of
|
|||||||
[Blackfriday](https://github.com/russross/blackfriday/tree/v2).
|
[Blackfriday](https://github.com/russross/blackfriday/tree/v2).
|
||||||
|
|
||||||
```
|
```
|
||||||
$ go get -u github.com/Depado/bfchroma
|
$ go get github.com/Depado/bfchroma
|
||||||
```
|
```
|
||||||
|
|
||||||
_This project uses the module approach of go 1.11_
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
This renderer integrates chroma to highlight code with triple backtick notation.
|
This renderer integrates chroma to highlight code with triple backtick notation.
|
||||||
Generated
Vendored
+9
-9
@@ -5,10 +5,10 @@ package bfchroma
|
|||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/alecthomas/chroma"
|
"github.com/alecthomas/chroma/v2"
|
||||||
"github.com/alecthomas/chroma/formatters/html"
|
"github.com/alecthomas/chroma/v2/formatters/html"
|
||||||
"github.com/alecthomas/chroma/lexers"
|
"github.com/alecthomas/chroma/v2/lexers"
|
||||||
"github.com/alecthomas/chroma/styles"
|
"github.com/alecthomas/chroma/v2/styles"
|
||||||
bf "github.com/russross/blackfriday/v2"
|
bf "github.com/russross/blackfriday/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ type Renderer struct {
|
|||||||
ChromaOptions []html.Option
|
ChromaOptions []html.Option
|
||||||
Style *chroma.Style
|
Style *chroma.Style
|
||||||
Formatter *html.Formatter
|
Formatter *html.Formatter
|
||||||
embedCSS bool
|
embedCSS bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderNode satisfies the Renderer interface
|
// RenderNode satisfies the Renderer interface
|
||||||
@@ -116,9 +116,9 @@ func (r *Renderer) RenderNode(w io.Writer, node *bf.Node, entering bool) bf.Walk
|
|||||||
switch node.Type {
|
switch node.Type {
|
||||||
case bf.Document:
|
case bf.Document:
|
||||||
if entering && r.embedCSS {
|
if entering && r.embedCSS {
|
||||||
w.Write([]byte("<style>"))
|
w.Write([]byte("<style>")) // nolint: errcheck
|
||||||
r.Formatter.WriteCSS(w, r.Style)
|
r.Formatter.WriteCSS(w, r.Style) // nolint: errcheck
|
||||||
w.Write([]byte("</style>"))
|
w.Write([]byte("</style>")) // nolint: errcheck
|
||||||
}
|
}
|
||||||
return r.Base.RenderNode(w, node, entering)
|
return r.Base.RenderNode(w, node, entering)
|
||||||
case bf.CodeBlock:
|
case bf.CodeBlock:
|
||||||
@@ -144,4 +144,4 @@ func (r *Renderer) RenderFooter(w io.Writer, ast *bf.Node) {
|
|||||||
// ChromaCSS returns CSS used with chroma's html.WithClasses() option
|
// ChromaCSS returns CSS used with chroma's html.WithClasses() option
|
||||||
func (r *Renderer) ChromaCSS(w io.Writer) error {
|
func (r *Renderer) ChromaCSS(w io.Writer) error {
|
||||||
return r.Formatter.WriteCSS(w, r.Style)
|
return r.Formatter.WriteCSS(w, r.Style)
|
||||||
}
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
## Copyright 2014 Alvaro J. Genial. All rights reserved.
|
||||||
|
## Use of this source code is governed by a BSD-style
|
||||||
|
## license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- tip
|
||||||
|
- 1.6
|
||||||
|
- 1.5
|
||||||
|
- 1.4
|
||||||
|
- 1.3
|
||||||
|
# 1.2
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
# - go get -v golang.org/x/tools/cmd/cover
|
||||||
|
# - go get -v golang.org/x/tools/cmd/vet
|
||||||
|
# - go get -v golang.org/x/lint/golint
|
||||||
|
- export PATH=$PATH:/home/travis/gopath/bin
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go build -v ./...
|
||||||
|
- go test -v -cover ./...
|
||||||
|
- go vet ./...
|
||||||
|
# - golint .
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2014 Alvaro J. Genial. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
+247
@@ -0,0 +1,247 @@
|
|||||||
|
form
|
||||||
|
====
|
||||||
|
|
||||||
|
A Form Encoding & Decoding Package for Go, written by [Alvaro J. Genial](http://alva.ro).
|
||||||
|
|
||||||
|
[](https://travis-ci.org/ajg/form)
|
||||||
|
[](https://godoc.org/github.com/ajg/form)
|
||||||
|
|
||||||
|
Synopsis
|
||||||
|
--------
|
||||||
|
|
||||||
|
This library is designed to allow seamless, high-fidelity encoding and decoding of arbitrary data in `application/x-www-form-urlencoded` format and as [`url.Values`](http://golang.org/pkg/net/url/#Values). It is intended to be useful primarily in dealing with web forms and URI query strings, both of which natively employ said format.
|
||||||
|
|
||||||
|
Unsurprisingly, `form` is modeled after other Go [`encoding`](http://golang.org/pkg/encoding/) packages, in particular [`encoding/json`](http://golang.org/pkg/encoding/json/), and follows the same conventions (see below for more.) It aims to automatically handle any kind of concrete Go [data value](#values) (i.e., not functions, channels, etc.) while providing mechanisms for custom behavior.
|
||||||
|
|
||||||
|
Status
|
||||||
|
------
|
||||||
|
|
||||||
|
The implementation is in usable shape and is fairly well tested with its accompanying test suite. The API is unlikely to change much, but still may. Lastly, the code has not yet undergone a security review to ensure it is free of vulnerabilities. Please file an issue or send a pull request for fixes & improvements.
|
||||||
|
|
||||||
|
Dependencies
|
||||||
|
------------
|
||||||
|
|
||||||
|
The only requirement is [Go 1.2](http://golang.org/doc/go1.2) or later.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/ajg/form"
|
||||||
|
// or: "gopkg.in/ajg/form.v1"
|
||||||
|
```
|
||||||
|
|
||||||
|
Given a type like the following...
|
||||||
|
|
||||||
|
```go
|
||||||
|
type User struct {
|
||||||
|
Name string `form:"name"`
|
||||||
|
Email string `form:"email"`
|
||||||
|
Joined time.Time `form:"joined,omitempty"`
|
||||||
|
Posts []int `form:"posts"`
|
||||||
|
Preferences map[string]string `form:"prefs"`
|
||||||
|
Avatar []byte `form:"avatar"`
|
||||||
|
PasswordHash int64 `form:"-"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
...it is easy to encode data of that type...
|
||||||
|
|
||||||
|
|
||||||
|
```go
|
||||||
|
func PostUser(url string, u User) error {
|
||||||
|
var c http.Client
|
||||||
|
_, err := c.PostForm(url, form.EncodeToValues(u))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
...as well as decode it...
|
||||||
|
|
||||||
|
|
||||||
|
```go
|
||||||
|
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var u User
|
||||||
|
|
||||||
|
d := form.NewDecoder(r.Body)
|
||||||
|
if err := d.Decode(&u); err != nil {
|
||||||
|
http.Error(w, "Form could not be decoded", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "Decoded: %#v", u)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
...without having to do any grunt work.
|
||||||
|
|
||||||
|
Field Tags
|
||||||
|
----------
|
||||||
|
|
||||||
|
Like other encoding packages, `form` supports the following options for fields:
|
||||||
|
|
||||||
|
- `` `form:"-"` ``: Causes the field to be ignored during encoding and decoding.
|
||||||
|
- `` `form:"<name>"` ``: Overrides the field's name; useful especially when dealing with external identifiers in camelCase, as are commonly found on the web.
|
||||||
|
- `` `form:",omitempty"` ``: Elides the field during encoding if it is empty (typically meaning equal to the type's zero value.)
|
||||||
|
- `` `form:"<name>,omitempty"` ``: The way to combine the two options above.
|
||||||
|
|
||||||
|
Values
|
||||||
|
------
|
||||||
|
|
||||||
|
### Simple Values
|
||||||
|
|
||||||
|
Values of the following types are all considered simple:
|
||||||
|
|
||||||
|
- `bool`
|
||||||
|
- `int`, `int8`, `int16`, `int32`, `int64`, `rune`
|
||||||
|
- `uint`, `uint8`, `uint16`, `uint32`, `uint64`, `byte`
|
||||||
|
- `float32`, `float64`
|
||||||
|
- `complex64`, `complex128`
|
||||||
|
- `string`
|
||||||
|
- `[]byte` (see note)
|
||||||
|
- [`time.Time`](http://golang.org/pkg/time/#Time)
|
||||||
|
- [`url.URL`](http://golang.org/pkg/net/url/#URL)
|
||||||
|
- An alias of any of the above
|
||||||
|
- A pointer to any of the above
|
||||||
|
|
||||||
|
### Composite Values
|
||||||
|
|
||||||
|
A composite value is one that can contain other values. Values of the following kinds...
|
||||||
|
|
||||||
|
- Maps
|
||||||
|
- Slices; except `[]byte` (see note)
|
||||||
|
- Structs; except [`time.Time`](http://golang.org/pkg/time/#Time) and [`url.URL`](http://golang.org/pkg/net/url/#URL)
|
||||||
|
- Arrays
|
||||||
|
- An alias of any of the above
|
||||||
|
- A pointer to any of the above
|
||||||
|
|
||||||
|
...are considered composites in general, unless they implement custom marshaling/unmarshaling. Composite values are encoded as a flat mapping of paths to values, where the paths are constructed by joining the parent and child paths with a period (`.`).
|
||||||
|
|
||||||
|
(Note: a byte slice is treated as a `string` by default because it's more efficient, but can also be decoded as a slice—i.e., with indexes.)
|
||||||
|
|
||||||
|
### Untyped Values
|
||||||
|
|
||||||
|
While encouraged, it is not necessary to define a type (e.g. a `struct`) in order to use `form`, since it is able to encode and decode untyped data generically using the following rules:
|
||||||
|
|
||||||
|
- Simple values will be treated as a `string`.
|
||||||
|
- Composite values will be treated as a `map[string]interface{}`, itself able to contain nested values (both scalar and compound) ad infinitum.
|
||||||
|
- However, if there is a value (of any supported type) already present in a map for a given key, then it will be used when possible, rather than being replaced with a generic value as specified above; this makes it possible to handle partially typed, dynamic or schema-less values.
|
||||||
|
|
||||||
|
### Zero Values
|
||||||
|
|
||||||
|
By default, and without custom marshaling, zero values (also known as empty/default values) are encoded as the empty string. To disable this behavior, meaning to keep zero values in their literal form (e.g. `0` for integral types), `Encoder` offers a `KeepZeros` setter method, which will do just that when set to `true`.
|
||||||
|
|
||||||
|
### Unsupported Values
|
||||||
|
|
||||||
|
Values of the following kinds aren't supported and, if present, must be ignored.
|
||||||
|
|
||||||
|
- Channel
|
||||||
|
- Function
|
||||||
|
- Unsafe pointer
|
||||||
|
- An alias of any of the above
|
||||||
|
- A pointer to any of the above
|
||||||
|
|
||||||
|
Custom Marshaling
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
There is a default (generally lossless) marshaling & unmarshaling scheme for any concrete data value in Go, which is good enough in most cases. However, it is possible to override it and use a custom scheme. For instance, a "binary" field could be marshaled more efficiently using [base64](http://golang.org/pkg/encoding/base64/) to prevent it from being percent-escaped during serialization to `application/x-www-form-urlencoded` format.
|
||||||
|
|
||||||
|
Because `form` provides support for [`encoding.TextMarshaler`](http://golang.org/pkg/encoding/#TextMarshaler) and [`encoding.TextUnmarshaler`](http://golang.org/pkg/encoding/#TextUnmarshaler) it is easy to do that; for instance, like this:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "encoding"
|
||||||
|
|
||||||
|
type Binary []byte
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ encoding.TextMarshaler = &Binary{}
|
||||||
|
_ encoding.TextUnmarshaler = &Binary{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b Binary) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(base64.URLEncoding.EncodeToString([]byte(b))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Binary) UnmarshalText(text []byte) error {
|
||||||
|
bs, err := base64.URLEncoding.DecodeString(string(text))
|
||||||
|
if err == nil {
|
||||||
|
*b = Binary(bs)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now any value with type `Binary` will automatically be encoded using the [URL](http://golang.org/pkg/encoding/base64/#URLEncoding) variant of base64. It is left as an exercise to the reader to improve upon this scheme by eliminating the need for padding (which, besides being superfluous, uses `=`, a character that will end up percent-escaped.)
|
||||||
|
|
||||||
|
Keys
|
||||||
|
----
|
||||||
|
|
||||||
|
In theory any value can be a key as long as it has a string representation. However, by default, periods have special meaning to `form`, and thus, under the hood (i.e. in encoded form) they are transparently escaped using a preceding backslash (`\`). Backslashes within keys, themselves, are also escaped in this manner (e.g. as `\\`) in order to permit representing `\.` itself (as `\\\.`).
|
||||||
|
|
||||||
|
(Note: it is normally unnecessary to deal with this issue unless keys are being constructed manually—e.g. literally embedded in HTML or in a URI.)
|
||||||
|
|
||||||
|
The default delimiter and escape characters used for encoding and decoding composite keys can be changed using the `DelimitWith` and `EscapeWith` setter methods of `Encoder` and `Decoder`, respectively. For example...
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/ajg/form"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
type B struct {
|
||||||
|
Qux string `form:"qux"`
|
||||||
|
}
|
||||||
|
type A struct {
|
||||||
|
FooBar B `form:"foo.bar"`
|
||||||
|
}
|
||||||
|
a := A{FooBar: B{"XYZ"}}
|
||||||
|
os.Stdout.WriteString("Default: ")
|
||||||
|
form.NewEncoder(os.Stdout).Encode(a)
|
||||||
|
os.Stdout.WriteString("\nCustom: ")
|
||||||
|
form.NewEncoder(os.Stdout).DelimitWith('/').Encode(a)
|
||||||
|
os.Stdout.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
...will produce...
|
||||||
|
|
||||||
|
```
|
||||||
|
Default: foo%5C.bar.qux=XYZ
|
||||||
|
Custom: foo.bar%2Fqux=XYZ
|
||||||
|
```
|
||||||
|
|
||||||
|
(`%5C` and `%2F` represent `\` and `/`, respectively.)
|
||||||
|
|
||||||
|
Limitations
|
||||||
|
-----------
|
||||||
|
|
||||||
|
- Circular (self-referential) values are untested.
|
||||||
|
|
||||||
|
Future Work
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The following items would be nice to have in the future—though they are not being worked on yet:
|
||||||
|
|
||||||
|
- An option to treat all values as if they had been tagged with `omitempty`.
|
||||||
|
- An option to automatically treat all field names in `camelCase` or `underscore_case`.
|
||||||
|
- Built-in support for the types in [`math/big`](http://golang.org/pkg/math/big/).
|
||||||
|
- Built-in support for the types in [`image/color`](http://golang.org/pkg/image/color/).
|
||||||
|
- Improve encoding/decoding by reading/writing directly from/to the `io.Reader`/`io.Writer` when possible, rather than going through an intermediate representation (i.e. `node`) which requires more memory.
|
||||||
|
|
||||||
|
(Feel free to implement any of these and then send a pull request.)
|
||||||
|
|
||||||
|
Related Work
|
||||||
|
------------
|
||||||
|
|
||||||
|
- Package [gorilla/schema](https://github.com/gorilla/schema), which only implements decoding.
|
||||||
|
- Package [google/go-querystring](https://github.com/google/go-querystring), which only implements encoding.
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
This library is distributed under a BSD-style [LICENSE](./LICENSE).
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
TODO
|
||||||
|
====
|
||||||
|
|
||||||
|
- Document IgnoreCase and IgnoreUnknownKeys in README.
|
||||||
+370
@@ -0,0 +1,370 @@
|
|||||||
|
// Copyright 2014 Alvaro J. Genial. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package form
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewDecoder returns a new form Decoder.
|
||||||
|
func NewDecoder(r io.Reader) *Decoder {
|
||||||
|
return &Decoder{r, defaultDelimiter, defaultEscape, false, false}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decoder decodes data from a form (application/x-www-form-urlencoded).
|
||||||
|
type Decoder struct {
|
||||||
|
r io.Reader
|
||||||
|
d rune
|
||||||
|
e rune
|
||||||
|
ignoreUnknown bool
|
||||||
|
ignoreCase bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelimitWith sets r as the delimiter used for composite keys by Decoder d and returns the latter; it is '.' by default.
|
||||||
|
func (d *Decoder) DelimitWith(r rune) *Decoder {
|
||||||
|
d.d = r
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// EscapeWith sets r as the escape used for delimiters (and to escape itself) by Decoder d and returns the latter; it is '\\' by default.
|
||||||
|
func (d *Decoder) EscapeWith(r rune) *Decoder {
|
||||||
|
d.e = r
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode reads in and decodes form-encoded data into dst.
|
||||||
|
func (d Decoder) Decode(dst interface{}) error {
|
||||||
|
bs, err := ioutil.ReadAll(d.r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
vs, err := url.ParseQuery(string(bs))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v := reflect.ValueOf(dst)
|
||||||
|
return d.decodeNode(v, parseValues(d.d, d.e, vs, canIndexOrdinally(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IgnoreUnknownKeys if set to true it will make the Decoder ignore values
|
||||||
|
// that are not found in the destination object instead of returning an error.
|
||||||
|
func (d *Decoder) IgnoreUnknownKeys(ignoreUnknown bool) {
|
||||||
|
d.ignoreUnknown = ignoreUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
// IgnoreCase if set to true it will make the Decoder try to set values in the
|
||||||
|
// destination object even if the case does not match.
|
||||||
|
func (d *Decoder) IgnoreCase(ignoreCase bool) {
|
||||||
|
d.ignoreCase = ignoreCase
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeString decodes src into dst.
|
||||||
|
func (d Decoder) DecodeString(dst interface{}, src string) error {
|
||||||
|
vs, err := url.ParseQuery(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v := reflect.ValueOf(dst)
|
||||||
|
return d.decodeNode(v, parseValues(d.d, d.e, vs, canIndexOrdinally(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeValues decodes vs into dst.
|
||||||
|
func (d Decoder) DecodeValues(dst interface{}, vs url.Values) error {
|
||||||
|
v := reflect.ValueOf(dst)
|
||||||
|
return d.decodeNode(v, parseValues(d.d, d.e, vs, canIndexOrdinally(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeString decodes src into dst.
|
||||||
|
func DecodeString(dst interface{}, src string) error {
|
||||||
|
return NewDecoder(nil).DecodeString(dst, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeValues decodes vs into dst.
|
||||||
|
func DecodeValues(dst interface{}, vs url.Values) error {
|
||||||
|
return NewDecoder(nil).DecodeValues(dst, vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Decoder) decodeNode(v reflect.Value, n node) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
err = fmt.Errorf("%v", e)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if v.Kind() == reflect.Slice {
|
||||||
|
return fmt.Errorf("could not decode directly into slice; use pointer to slice")
|
||||||
|
}
|
||||||
|
d.decodeValue(v, n)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Decoder) decodeValue(v reflect.Value, x interface{}) {
|
||||||
|
t := v.Type()
|
||||||
|
k := v.Kind()
|
||||||
|
|
||||||
|
if k == reflect.Ptr && v.IsNil() {
|
||||||
|
v.Set(reflect.New(t.Elem()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if unmarshalValue(v, x) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
empty := isEmpty(x)
|
||||||
|
|
||||||
|
switch k {
|
||||||
|
case reflect.Ptr:
|
||||||
|
d.decodeValue(v.Elem(), x)
|
||||||
|
return
|
||||||
|
case reflect.Interface:
|
||||||
|
if !v.IsNil() {
|
||||||
|
d.decodeValue(v.Elem(), x)
|
||||||
|
return
|
||||||
|
|
||||||
|
} else if empty {
|
||||||
|
return // Allow nil interfaces only if empty.
|
||||||
|
} else {
|
||||||
|
panic("form: cannot decode non-empty value into into nil interface")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if empty {
|
||||||
|
v.Set(reflect.Zero(t)) // Treat the empty string as the zero value.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch k {
|
||||||
|
case reflect.Struct:
|
||||||
|
if t.ConvertibleTo(timeType) {
|
||||||
|
d.decodeTime(v, x)
|
||||||
|
} else if t.ConvertibleTo(urlType) {
|
||||||
|
d.decodeURL(v, x)
|
||||||
|
} else {
|
||||||
|
d.decodeStruct(v, x)
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
d.decodeSlice(v, x)
|
||||||
|
case reflect.Array:
|
||||||
|
d.decodeArray(v, x)
|
||||||
|
case reflect.Map:
|
||||||
|
d.decodeMap(v, x)
|
||||||
|
case reflect.Invalid, reflect.Uintptr, reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
|
panic(t.String() + " has unsupported kind " + k.String())
|
||||||
|
default:
|
||||||
|
d.decodeBasic(v, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Decoder) decodeStruct(v reflect.Value, x interface{}) {
|
||||||
|
t := v.Type()
|
||||||
|
for k, c := range getNode(x) {
|
||||||
|
if f, ok := findField(v, k, d.ignoreCase); !ok && k == "" {
|
||||||
|
panic(getString(x) + " cannot be decoded as " + t.String())
|
||||||
|
} else if !ok {
|
||||||
|
if !d.ignoreUnknown {
|
||||||
|
panic(k + " doesn't exist in " + t.String())
|
||||||
|
}
|
||||||
|
} else if !f.CanSet() {
|
||||||
|
panic(k + " cannot be set in " + t.String())
|
||||||
|
} else {
|
||||||
|
d.decodeValue(f, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Decoder) decodeMap(v reflect.Value, x interface{}) {
|
||||||
|
t := v.Type()
|
||||||
|
if v.IsNil() {
|
||||||
|
v.Set(reflect.MakeMap(t))
|
||||||
|
}
|
||||||
|
for k, c := range getNode(x) {
|
||||||
|
i := reflect.New(t.Key()).Elem()
|
||||||
|
d.decodeValue(i, k)
|
||||||
|
|
||||||
|
w := v.MapIndex(i)
|
||||||
|
if w.IsValid() { // We have an actual element value to decode into.
|
||||||
|
if w.Kind() == reflect.Interface {
|
||||||
|
w = w.Elem()
|
||||||
|
}
|
||||||
|
w = reflect.New(w.Type()).Elem()
|
||||||
|
} else if t.Elem().Kind() != reflect.Interface { // The map's element type is concrete.
|
||||||
|
w = reflect.New(t.Elem()).Elem()
|
||||||
|
} else {
|
||||||
|
// The best we can do here is to decode as either a string (for scalars) or a map[string]interface {} (for the rest).
|
||||||
|
// We could try to guess the type based on the string (e.g. true/false => bool) but that'll get ugly fast,
|
||||||
|
// especially if we have to guess the kind (slice vs. array vs. map) and index type (e.g. string, int, etc.)
|
||||||
|
switch c.(type) {
|
||||||
|
case node:
|
||||||
|
w = reflect.MakeMap(stringMapType)
|
||||||
|
case string:
|
||||||
|
w = reflect.New(stringType).Elem()
|
||||||
|
default:
|
||||||
|
panic("value is neither node nor string")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.decodeValue(w, c)
|
||||||
|
v.SetMapIndex(i, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Decoder) decodeArray(v reflect.Value, x interface{}) {
|
||||||
|
t := v.Type()
|
||||||
|
for k, c := range getNode(x) {
|
||||||
|
i, err := strconv.Atoi(k)
|
||||||
|
if err != nil {
|
||||||
|
panic(k + " is not a valid index for type " + t.String())
|
||||||
|
}
|
||||||
|
if l := v.Len(); i >= l {
|
||||||
|
panic("index is above array size")
|
||||||
|
}
|
||||||
|
d.decodeValue(v.Index(i), c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Decoder) decodeSlice(v reflect.Value, x interface{}) {
|
||||||
|
t := v.Type()
|
||||||
|
if t.Elem().Kind() == reflect.Uint8 {
|
||||||
|
// Allow, but don't require, byte slices to be encoded as a single string.
|
||||||
|
if s, ok := x.(string); ok {
|
||||||
|
v.SetBytes([]byte(s))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Implicit indexing is currently done at the parseValues level,
|
||||||
|
// so if if an implicitKey reaches here it will always replace the last.
|
||||||
|
implicit := 0
|
||||||
|
for k, c := range getNode(x) {
|
||||||
|
var i int
|
||||||
|
if k == implicitKey {
|
||||||
|
i = implicit
|
||||||
|
implicit++
|
||||||
|
} else {
|
||||||
|
explicit, err := strconv.Atoi(k)
|
||||||
|
if err != nil {
|
||||||
|
panic(k + " is not a valid index for type " + t.String())
|
||||||
|
}
|
||||||
|
i = explicit
|
||||||
|
implicit = explicit + 1
|
||||||
|
}
|
||||||
|
// "Extend" the slice if it's too short.
|
||||||
|
if l := v.Len(); i >= l {
|
||||||
|
delta := i - l + 1
|
||||||
|
v.Set(reflect.AppendSlice(v, reflect.MakeSlice(t, delta, delta)))
|
||||||
|
}
|
||||||
|
d.decodeValue(v.Index(i), c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Decoder) decodeBasic(v reflect.Value, x interface{}) {
|
||||||
|
t := v.Type()
|
||||||
|
switch k, s := t.Kind(), getString(x); k {
|
||||||
|
case reflect.Bool:
|
||||||
|
if b, e := strconv.ParseBool(s); e == nil {
|
||||||
|
v.SetBool(b)
|
||||||
|
} else {
|
||||||
|
panic("could not parse bool from " + strconv.Quote(s))
|
||||||
|
}
|
||||||
|
case reflect.Int,
|
||||||
|
reflect.Int8,
|
||||||
|
reflect.Int16,
|
||||||
|
reflect.Int32,
|
||||||
|
reflect.Int64:
|
||||||
|
if i, e := strconv.ParseInt(s, 10, 64); e == nil {
|
||||||
|
v.SetInt(i)
|
||||||
|
} else {
|
||||||
|
panic("could not parse int from " + strconv.Quote(s))
|
||||||
|
}
|
||||||
|
case reflect.Uint,
|
||||||
|
reflect.Uint8,
|
||||||
|
reflect.Uint16,
|
||||||
|
reflect.Uint32,
|
||||||
|
reflect.Uint64:
|
||||||
|
if u, e := strconv.ParseUint(s, 10, 64); e == nil {
|
||||||
|
v.SetUint(u)
|
||||||
|
} else {
|
||||||
|
panic("could not parse uint from " + strconv.Quote(s))
|
||||||
|
}
|
||||||
|
case reflect.Float32,
|
||||||
|
reflect.Float64:
|
||||||
|
if f, e := strconv.ParseFloat(s, 64); e == nil {
|
||||||
|
v.SetFloat(f)
|
||||||
|
} else {
|
||||||
|
panic("could not parse float from " + strconv.Quote(s))
|
||||||
|
}
|
||||||
|
case reflect.Complex64,
|
||||||
|
reflect.Complex128:
|
||||||
|
var c complex128
|
||||||
|
if n, err := fmt.Sscanf(s, "%g", &c); n == 1 && err == nil {
|
||||||
|
v.SetComplex(c)
|
||||||
|
} else {
|
||||||
|
panic("could not parse complex from " + strconv.Quote(s))
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
v.SetString(s)
|
||||||
|
default:
|
||||||
|
panic(t.String() + " has unsupported kind " + k.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Decoder) decodeTime(v reflect.Value, x interface{}) {
|
||||||
|
t := v.Type()
|
||||||
|
s := getString(x)
|
||||||
|
// TODO: Find a more efficient way to do this.
|
||||||
|
for _, f := range allowedTimeFormats {
|
||||||
|
if p, err := time.Parse(f, s); err == nil {
|
||||||
|
v.Set(reflect.ValueOf(p).Convert(v.Type()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("cannot decode string `" + s + "` as " + t.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Decoder) decodeURL(v reflect.Value, x interface{}) {
|
||||||
|
t := v.Type()
|
||||||
|
s := getString(x)
|
||||||
|
if u, err := url.Parse(s); err == nil {
|
||||||
|
v.Set(reflect.ValueOf(*u).Convert(v.Type()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
panic("cannot decode string `" + s + "` as " + t.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var allowedTimeFormats = []string{
|
||||||
|
"2006-01-02T15:04:05.999999999Z07:00",
|
||||||
|
"2006-01-02T15:04:05.999999999Z07",
|
||||||
|
"2006-01-02T15:04:05.999999999Z",
|
||||||
|
"2006-01-02T15:04:05.999999999",
|
||||||
|
"2006-01-02T15:04:05Z07:00",
|
||||||
|
"2006-01-02T15:04:05Z07",
|
||||||
|
"2006-01-02T15:04:05Z",
|
||||||
|
"2006-01-02T15:04:05",
|
||||||
|
"2006-01-02T15:04Z",
|
||||||
|
"2006-01-02T15:04",
|
||||||
|
"2006-01-02T15Z",
|
||||||
|
"2006-01-02T15",
|
||||||
|
"2006-01-02",
|
||||||
|
"2006-01",
|
||||||
|
"2006",
|
||||||
|
"15:04:05.999999999Z07:00",
|
||||||
|
"15:04:05.999999999Z07",
|
||||||
|
"15:04:05.999999999Z",
|
||||||
|
"15:04:05.999999999",
|
||||||
|
"15:04:05Z07:00",
|
||||||
|
"15:04:05Z07",
|
||||||
|
"15:04:05Z",
|
||||||
|
"15:04:05",
|
||||||
|
"15:04Z",
|
||||||
|
"15:04",
|
||||||
|
"15Z",
|
||||||
|
"15",
|
||||||
|
}
|
||||||
+388
@@ -0,0 +1,388 @@
|
|||||||
|
// Copyright 2014 Alvaro J. Genial. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package form
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewEncoder returns a new form Encoder.
|
||||||
|
func NewEncoder(w io.Writer) *Encoder {
|
||||||
|
return &Encoder{w, defaultDelimiter, defaultEscape, false}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoder provides a way to encode to a Writer.
|
||||||
|
type Encoder struct {
|
||||||
|
w io.Writer
|
||||||
|
d rune
|
||||||
|
e rune
|
||||||
|
z bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelimitWith sets r as the delimiter used for composite keys by Encoder e and returns the latter; it is '.' by default.
|
||||||
|
func (e *Encoder) DelimitWith(r rune) *Encoder {
|
||||||
|
e.d = r
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// EscapeWith sets r as the escape used for delimiters (and to escape itself) by Encoder e and returns the latter; it is '\\' by default.
|
||||||
|
func (e *Encoder) EscapeWith(r rune) *Encoder {
|
||||||
|
e.e = r
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeepZeros sets whether Encoder e should keep zero (default) values in their literal form when encoding, and returns the former; by default zero values are not kept, but are rather encoded as the empty string.
|
||||||
|
func (e *Encoder) KeepZeros(z bool) *Encoder {
|
||||||
|
e.z = z
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode encodes dst as form and writes it out using the Encoder's Writer.
|
||||||
|
func (e Encoder) Encode(dst interface{}) error {
|
||||||
|
v := reflect.ValueOf(dst)
|
||||||
|
n, err := encodeToNode(v, e.z)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s := n.values(e.d, e.e).Encode()
|
||||||
|
l, err := io.WriteString(e.w, s)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
return err
|
||||||
|
case l != len(s):
|
||||||
|
return errors.New("could not write data completely")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeToString encodes dst as a form and returns it as a string.
|
||||||
|
func EncodeToString(dst interface{}) (string, error) {
|
||||||
|
v := reflect.ValueOf(dst)
|
||||||
|
n, err := encodeToNode(v, false)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
vs := n.values(defaultDelimiter, defaultEscape)
|
||||||
|
return vs.Encode(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeToValues encodes dst as a form and returns it as Values.
|
||||||
|
func EncodeToValues(dst interface{}) (url.Values, error) {
|
||||||
|
v := reflect.ValueOf(dst)
|
||||||
|
n, err := encodeToNode(v, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
vs := n.values(defaultDelimiter, defaultEscape)
|
||||||
|
return vs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeToNode(v reflect.Value, z bool) (n node, err error) {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
err = fmt.Errorf("%v", e)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return getNode(encodeValue(v, z)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeValue(v reflect.Value, z bool) interface{} {
|
||||||
|
t := v.Type()
|
||||||
|
k := v.Kind()
|
||||||
|
|
||||||
|
if s, ok := marshalValue(v); ok {
|
||||||
|
return s
|
||||||
|
} else if !z && isEmptyValue(v) {
|
||||||
|
return "" // Treat the zero value as the empty string.
|
||||||
|
}
|
||||||
|
|
||||||
|
switch k {
|
||||||
|
case reflect.Ptr, reflect.Interface:
|
||||||
|
return encodeValue(v.Elem(), z)
|
||||||
|
case reflect.Struct:
|
||||||
|
if t.ConvertibleTo(timeType) {
|
||||||
|
return encodeTime(v)
|
||||||
|
} else if t.ConvertibleTo(urlType) {
|
||||||
|
return encodeURL(v)
|
||||||
|
}
|
||||||
|
return encodeStruct(v, z)
|
||||||
|
case reflect.Slice:
|
||||||
|
return encodeSlice(v, z)
|
||||||
|
case reflect.Array:
|
||||||
|
return encodeArray(v, z)
|
||||||
|
case reflect.Map:
|
||||||
|
return encodeMap(v, z)
|
||||||
|
case reflect.Invalid, reflect.Uintptr, reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
|
panic(t.String() + " has unsupported kind " + t.Kind().String())
|
||||||
|
default:
|
||||||
|
return encodeBasic(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeStruct(v reflect.Value, z bool) interface{} {
|
||||||
|
t := v.Type()
|
||||||
|
n := node{}
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
f := t.Field(i)
|
||||||
|
k, oe := fieldInfo(f)
|
||||||
|
|
||||||
|
if k == "-" {
|
||||||
|
continue
|
||||||
|
} else if fv := v.Field(i); oe && isEmptyValue(fv) {
|
||||||
|
delete(n, k)
|
||||||
|
} else {
|
||||||
|
n[k] = encodeValue(fv, z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeMap(v reflect.Value, z bool) interface{} {
|
||||||
|
n := node{}
|
||||||
|
for _, i := range v.MapKeys() {
|
||||||
|
k := getString(encodeValue(i, z))
|
||||||
|
n[k] = encodeValue(v.MapIndex(i), z)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeArray(v reflect.Value, z bool) interface{} {
|
||||||
|
n := node{}
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
n[strconv.Itoa(i)] = encodeValue(v.Index(i), z)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeSlice(v reflect.Value, z bool) interface{} {
|
||||||
|
t := v.Type()
|
||||||
|
if t.Elem().Kind() == reflect.Uint8 {
|
||||||
|
return string(v.Bytes()) // Encode byte slices as a single string by default.
|
||||||
|
}
|
||||||
|
n := node{}
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
n[strconv.Itoa(i)] = encodeValue(v.Index(i), z)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeTime(v reflect.Value) string {
|
||||||
|
t := v.Convert(timeType).Interface().(time.Time)
|
||||||
|
if t.Year() == 0 && (t.Month() == 0 || t.Month() == 1) && (t.Day() == 0 || t.Day() == 1) {
|
||||||
|
return t.Format("15:04:05.999999999Z07:00")
|
||||||
|
} else if t.Hour() == 0 && t.Minute() == 0 && t.Second() == 0 && t.Nanosecond() == 0 {
|
||||||
|
return t.Format("2006-01-02")
|
||||||
|
}
|
||||||
|
return t.Format("2006-01-02T15:04:05.999999999Z07:00")
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeURL(v reflect.Value) string {
|
||||||
|
u := v.Convert(urlType).Interface().(url.URL)
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeBasic(v reflect.Value) string {
|
||||||
|
t := v.Type()
|
||||||
|
switch k := t.Kind(); k {
|
||||||
|
case reflect.Bool:
|
||||||
|
return strconv.FormatBool(v.Bool())
|
||||||
|
case reflect.Int,
|
||||||
|
reflect.Int8,
|
||||||
|
reflect.Int16,
|
||||||
|
reflect.Int32,
|
||||||
|
reflect.Int64:
|
||||||
|
return strconv.FormatInt(v.Int(), 10)
|
||||||
|
case reflect.Uint,
|
||||||
|
reflect.Uint8,
|
||||||
|
reflect.Uint16,
|
||||||
|
reflect.Uint32,
|
||||||
|
reflect.Uint64:
|
||||||
|
return strconv.FormatUint(v.Uint(), 10)
|
||||||
|
case reflect.Float32:
|
||||||
|
return strconv.FormatFloat(v.Float(), 'g', -1, 32)
|
||||||
|
case reflect.Float64:
|
||||||
|
return strconv.FormatFloat(v.Float(), 'g', -1, 64)
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
s := fmt.Sprintf("%g", v.Complex())
|
||||||
|
return strings.TrimSuffix(strings.TrimPrefix(s, "("), ")")
|
||||||
|
case reflect.String:
|
||||||
|
return v.String()
|
||||||
|
}
|
||||||
|
panic(t.String() + " has unsupported kind " + t.Kind().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEmptyValue(v reflect.Value) bool {
|
||||||
|
switch t := v.Type(); v.Kind() {
|
||||||
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||||
|
return v.Len() == 0
|
||||||
|
case reflect.Bool:
|
||||||
|
return !v.Bool()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return v.Int() == 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return v.Uint() == 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return v.Float() == 0
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
return v.Complex() == 0
|
||||||
|
case reflect.Interface, reflect.Ptr:
|
||||||
|
return v.IsNil()
|
||||||
|
case reflect.Struct:
|
||||||
|
if t.ConvertibleTo(timeType) {
|
||||||
|
return v.Convert(timeType).Interface().(time.Time).IsZero()
|
||||||
|
}
|
||||||
|
return reflect.DeepEqual(v, reflect.Zero(t))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// canIndexOrdinally returns whether a value contains an ordered sequence of elements.
|
||||||
|
func canIndexOrdinally(v reflect.Value) bool {
|
||||||
|
if !v.IsValid() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch t := v.Type(); t.Kind() {
|
||||||
|
case reflect.Ptr, reflect.Interface:
|
||||||
|
return canIndexOrdinally(v.Elem())
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func fieldInfo(f reflect.StructField) (k string, oe bool) {
|
||||||
|
if f.PkgPath != "" { // Skip private fields.
|
||||||
|
return omittedKey, oe
|
||||||
|
}
|
||||||
|
|
||||||
|
k = f.Name
|
||||||
|
tag := f.Tag.Get("form")
|
||||||
|
if tag == "" {
|
||||||
|
return k, oe
|
||||||
|
}
|
||||||
|
|
||||||
|
ps := strings.SplitN(tag, ",", 2)
|
||||||
|
if ps[0] != "" {
|
||||||
|
k = ps[0]
|
||||||
|
}
|
||||||
|
if len(ps) == 2 {
|
||||||
|
oe = ps[1] == "omitempty"
|
||||||
|
}
|
||||||
|
return k, oe
|
||||||
|
}
|
||||||
|
|
||||||
|
func findField(v reflect.Value, n string, ignoreCase bool) (reflect.Value, bool) {
|
||||||
|
t := v.Type()
|
||||||
|
l := v.NumField()
|
||||||
|
|
||||||
|
var lowerN string
|
||||||
|
caseInsensitiveMatch := -1
|
||||||
|
if ignoreCase {
|
||||||
|
lowerN = strings.ToLower(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// First try named fields.
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
f := t.Field(i)
|
||||||
|
k, _ := fieldInfo(f)
|
||||||
|
if k == omittedKey {
|
||||||
|
continue
|
||||||
|
} else if n == k {
|
||||||
|
return v.Field(i), true
|
||||||
|
} else if ignoreCase && lowerN == strings.ToLower(k) {
|
||||||
|
caseInsensitiveMatch = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no exact match was found try case insensitive match.
|
||||||
|
if caseInsensitiveMatch != -1 {
|
||||||
|
return v.Field(caseInsensitiveMatch), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then try anonymous (embedded) fields.
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
f := t.Field(i)
|
||||||
|
k, _ := fieldInfo(f)
|
||||||
|
if k == omittedKey || !f.Anonymous { // || k != "" ?
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fv := v.Field(i)
|
||||||
|
fk := fv.Kind()
|
||||||
|
for fk == reflect.Ptr || fk == reflect.Interface {
|
||||||
|
fv = fv.Elem()
|
||||||
|
fk = fv.Kind()
|
||||||
|
}
|
||||||
|
|
||||||
|
if fk != reflect.Struct {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ev, ok := findField(fv, n, ignoreCase); ok {
|
||||||
|
return ev, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.Value{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
stringType = reflect.TypeOf(string(""))
|
||||||
|
stringMapType = reflect.TypeOf(map[string]interface{}{})
|
||||||
|
timeType = reflect.TypeOf(time.Time{})
|
||||||
|
timePtrType = reflect.TypeOf(&time.Time{})
|
||||||
|
urlType = reflect.TypeOf(url.URL{})
|
||||||
|
)
|
||||||
|
|
||||||
|
func skipTextMarshalling(t reflect.Type) bool {
|
||||||
|
/*// Skip time.Time because its text unmarshaling is overly rigid:
|
||||||
|
return t == timeType || t == timePtrType*/
|
||||||
|
// Skip time.Time & convertibles because its text unmarshaling is overly rigid:
|
||||||
|
return t.ConvertibleTo(timeType) || t.ConvertibleTo(timePtrType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalValue(v reflect.Value, x interface{}) bool {
|
||||||
|
if skipTextMarshalling(v.Type()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
tu, ok := v.Interface().(encoding.TextUnmarshaler)
|
||||||
|
if !ok && !v.CanAddr() {
|
||||||
|
return false
|
||||||
|
} else if !ok {
|
||||||
|
return unmarshalValue(v.Addr(), x)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := getString(x)
|
||||||
|
if err := tu.UnmarshalText([]byte(s)); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalValue(v reflect.Value) (string, bool) {
|
||||||
|
if skipTextMarshalling(v.Type()) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
tm, ok := v.Interface().(encoding.TextMarshaler)
|
||||||
|
if !ok && !v.CanAddr() {
|
||||||
|
return "", false
|
||||||
|
} else if !ok {
|
||||||
|
return marshalValue(v.Addr())
|
||||||
|
}
|
||||||
|
|
||||||
|
bs, err := tm.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return string(bs), true
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
// Copyright 2014 Alvaro J. Genial. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package form implements encoding and decoding of application/x-www-form-urlencoded data.
|
||||||
|
package form
|
||||||
|
|
||||||
|
const (
|
||||||
|
implicitKey = "_"
|
||||||
|
omittedKey = "-"
|
||||||
|
|
||||||
|
defaultDelimiter = '.'
|
||||||
|
defaultEscape = '\\'
|
||||||
|
)
|
||||||
+152
@@ -0,0 +1,152 @@
|
|||||||
|
// Copyright 2014 Alvaro J. Genial. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package form
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type node map[string]interface{}
|
||||||
|
|
||||||
|
func (n node) values(d, e rune) url.Values {
|
||||||
|
vs := url.Values{}
|
||||||
|
n.merge(d, e, "", &vs)
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n node) merge(d, e rune, p string, vs *url.Values) {
|
||||||
|
for k, x := range n {
|
||||||
|
switch y := x.(type) {
|
||||||
|
case string:
|
||||||
|
vs.Add(p+escape(d, e, k), y)
|
||||||
|
case node:
|
||||||
|
y.merge(d, e, p+escape(d, e, k)+string(d), vs)
|
||||||
|
default:
|
||||||
|
panic("value is neither string nor node")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add tests for implicit indexing.
|
||||||
|
func parseValues(d, e rune, vs url.Values, canIndexFirstLevelOrdinally bool) node {
|
||||||
|
// NOTE: Because of the flattening of potentially multiple strings to one key, implicit indexing works:
|
||||||
|
// i. At the first level; e.g. Foo.Bar=A&Foo.Bar=B becomes 0.Foo.Bar=A&1.Foo.Bar=B
|
||||||
|
// ii. At the last level; e.g. Foo.Bar._=A&Foo.Bar._=B becomes Foo.Bar.0=A&Foo.Bar.1=B
|
||||||
|
// TODO: At in-between levels; e.g. Foo._.Bar=A&Foo._.Bar=B becomes Foo.0.Bar=A&Foo.1.Bar=B
|
||||||
|
// (This last one requires that there only be one placeholder in order for it to be unambiguous.)
|
||||||
|
|
||||||
|
m := map[string]string{}
|
||||||
|
for k, ss := range vs {
|
||||||
|
indexLastLevelOrdinally := strings.HasSuffix(k, string(d)+implicitKey)
|
||||||
|
|
||||||
|
for i, s := range ss {
|
||||||
|
if canIndexFirstLevelOrdinally {
|
||||||
|
k = strconv.Itoa(i) + string(d) + k
|
||||||
|
} else if indexLastLevelOrdinally {
|
||||||
|
k = strings.TrimSuffix(k, implicitKey) + strconv.Itoa(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
m[k] = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n := node{}
|
||||||
|
for k, s := range m {
|
||||||
|
n = n.split(d, e, k, s)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitPath(d, e rune, path string) (k, rest string) {
|
||||||
|
esc := false
|
||||||
|
for i, r := range path {
|
||||||
|
switch {
|
||||||
|
case !esc && r == e:
|
||||||
|
esc = true
|
||||||
|
case !esc && r == d:
|
||||||
|
return unescape(d, e, path[:i]), path[i+1:]
|
||||||
|
default:
|
||||||
|
esc = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return unescape(d, e, path), ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n node) split(d, e rune, path, s string) node {
|
||||||
|
k, rest := splitPath(d, e, path)
|
||||||
|
if rest == "" {
|
||||||
|
return add(n, k, s)
|
||||||
|
}
|
||||||
|
if _, ok := n[k]; !ok {
|
||||||
|
n[k] = node{}
|
||||||
|
}
|
||||||
|
|
||||||
|
c := getNode(n[k])
|
||||||
|
n[k] = c.split(d, e, rest, s)
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func add(n node, k, s string) node {
|
||||||
|
if n == nil {
|
||||||
|
return node{k: s}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := n[k]; ok {
|
||||||
|
panic("key " + k + " already set")
|
||||||
|
}
|
||||||
|
|
||||||
|
n[k] = s
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEmpty(x interface{}) bool {
|
||||||
|
switch y := x.(type) {
|
||||||
|
case string:
|
||||||
|
return y == ""
|
||||||
|
case node:
|
||||||
|
if s, ok := y[""].(string); ok {
|
||||||
|
return s == ""
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
panic("value is neither string nor node")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNode(x interface{}) node {
|
||||||
|
switch y := x.(type) {
|
||||||
|
case string:
|
||||||
|
return node{"": y}
|
||||||
|
case node:
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
panic("value is neither string nor node")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getString(x interface{}) string {
|
||||||
|
switch y := x.(type) {
|
||||||
|
case string:
|
||||||
|
return y
|
||||||
|
case node:
|
||||||
|
if s, ok := y[""].(string); ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
panic("value is neither string nor node")
|
||||||
|
}
|
||||||
|
|
||||||
|
func escape(d, e rune, s string) string {
|
||||||
|
s = strings.Replace(s, string(e), string(e)+string(e), -1) // Escape the escape (\ => \\)
|
||||||
|
s = strings.Replace(s, string(d), string(e)+string(d), -1) // Escape the delimiter (. => \.)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func unescape(d, e rune, s string) string {
|
||||||
|
s = strings.Replace(s, string(e)+string(d), string(d), -1) // Unescape the delimiter (\. => .)
|
||||||
|
s = strings.Replace(s, string(e)+string(e), string(e), -1) // Unescape the escape (\\ => \)
|
||||||
|
return s
|
||||||
|
}
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/bash -eu
|
||||||
|
|
||||||
|
# TODO: Only colorize messages given a suitable terminal.
|
||||||
|
# FIXME: Handle case in which no stash entry is created due to no changes.
|
||||||
|
|
||||||
|
printf "\e[30m=== PRE-COMMIT STARTING ===\e[m\n"
|
||||||
|
git stash save --quiet --keep-index --include-untracked
|
||||||
|
|
||||||
|
if go build -v ./... && go test -v -cover ./... && go vet ./... && golint . && travis-lint; then
|
||||||
|
result=$?
|
||||||
|
printf "\e[32m=== PRE-COMMIT SUCCEEDED ===\e[m\n"
|
||||||
|
else
|
||||||
|
result=$?
|
||||||
|
printf "\e[31m=== PRE-COMMIT FAILED ===\e[m\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
git stash pop --quiet
|
||||||
|
exit $result
|
||||||
-60
@@ -1,60 +0,0 @@
|
|||||||
package a
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/alecthomas/chroma" // nolint
|
|
||||||
"github.com/alecthomas/chroma/lexers/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ABAP lexer.
|
|
||||||
var Abap = internal.Register(MustNewLazyLexer(
|
|
||||||
&Config{
|
|
||||||
Name: "ABAP",
|
|
||||||
Aliases: []string{"abap"},
|
|
||||||
Filenames: []string{"*.abap", "*.ABAP"},
|
|
||||||
MimeTypes: []string{"text/x-abap"},
|
|
||||||
CaseInsensitive: true,
|
|
||||||
},
|
|
||||||
abapRules,
|
|
||||||
))
|
|
||||||
|
|
||||||
func abapRules() Rules {
|
|
||||||
return Rules{
|
|
||||||
"common": {
|
|
||||||
{`\s+`, Text, nil},
|
|
||||||
{`^\*.*$`, CommentSingle, nil},
|
|
||||||
{`\".*?\n`, CommentSingle, nil},
|
|
||||||
{`##\w+`, CommentSpecial, nil},
|
|
||||||
},
|
|
||||||
"variable-names": {
|
|
||||||
{`<\S+>`, NameVariable, nil},
|
|
||||||
{`\w[\w~]*(?:(\[\])|->\*)?`, NameVariable, nil},
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
Include("common"),
|
|
||||||
{`CALL\s+(?:BADI|CUSTOMER-FUNCTION|FUNCTION)`, Keyword, nil},
|
|
||||||
{`(CALL\s+(?:DIALOG|SCREEN|SUBSCREEN|SELECTION-SCREEN|TRANSACTION|TRANSFORMATION))\b`, Keyword, nil},
|
|
||||||
{`(FORM|PERFORM)(\s+)(\w+)`, ByGroups(Keyword, Text, NameFunction), nil},
|
|
||||||
{`(PERFORM)(\s+)(\()(\w+)(\))`, ByGroups(Keyword, Text, Punctuation, NameVariable, Punctuation), nil},
|
|
||||||
{`(MODULE)(\s+)(\S+)(\s+)(INPUT|OUTPUT)`, ByGroups(Keyword, Text, NameFunction, Text, Keyword), nil},
|
|
||||||
{`(METHOD)(\s+)([\w~]+)`, ByGroups(Keyword, Text, NameFunction), nil},
|
|
||||||
{`(\s+)([\w\-]+)([=\-]>)([\w\-~]+)`, ByGroups(Text, NameVariable, Operator, NameFunction), nil},
|
|
||||||
{`(?<=(=|-)>)([\w\-~]+)(?=\()`, NameFunction, nil},
|
|
||||||
{`(TEXT)(-)(\d{3})`, ByGroups(Keyword, Punctuation, LiteralNumberInteger), nil},
|
|
||||||
{`(TEXT)(-)(\w{3})`, ByGroups(Keyword, Punctuation, NameVariable), nil},
|
|
||||||
{`(ADD-CORRESPONDING|AUTHORITY-CHECK|CLASS-DATA|CLASS-EVENTS|CLASS-METHODS|CLASS-POOL|DELETE-ADJACENT|DIVIDE-CORRESPONDING|EDITOR-CALL|ENHANCEMENT-POINT|ENHANCEMENT-SECTION|EXIT-COMMAND|FIELD-GROUPS|FIELD-SYMBOLS|FUNCTION-POOL|INTERFACE-POOL|INVERTED-DATE|LOAD-OF-PROGRAM|LOG-POINT|MESSAGE-ID|MOVE-CORRESPONDING|MULTIPLY-CORRESPONDING|NEW-LINE|NEW-PAGE|NEW-SECTION|NO-EXTENSION|OUTPUT-LENGTH|PRINT-CONTROL|SELECT-OPTIONS|START-OF-SELECTION|SUBTRACT-CORRESPONDING|SYNTAX-CHECK|SYSTEM-EXCEPTIONS|TYPE-POOL|TYPE-POOLS|NO-DISPLAY)\b`, Keyword, nil},
|
|
||||||
{`(?<![-\>])(CREATE\s+(PUBLIC|PRIVATE|DATA|OBJECT)|(PUBLIC|PRIVATE|PROTECTED)\s+SECTION|(TYPE|LIKE)\s+((LINE\s+OF|REF\s+TO|(SORTED|STANDARD|HASHED)\s+TABLE\s+OF))?|FROM\s+(DATABASE|MEMORY)|CALL\s+METHOD|(GROUP|ORDER) BY|HAVING|SEPARATED BY|GET\s+(BADI|BIT|CURSOR|DATASET|LOCALE|PARAMETER|PF-STATUS|(PROPERTY|REFERENCE)\s+OF|RUN\s+TIME|TIME\s+(STAMP)?)?|SET\s+(BIT|BLANK\s+LINES|COUNTRY|CURSOR|DATASET|EXTENDED\s+CHECK|HANDLER|HOLD\s+DATA|LANGUAGE|LEFT\s+SCROLL-BOUNDARY|LOCALE|MARGIN|PARAMETER|PF-STATUS|PROPERTY\s+OF|RUN\s+TIME\s+(ANALYZER|CLOCK\s+RESOLUTION)|SCREEN|TITLEBAR|UPADTE\s+TASK\s+LOCAL|USER-COMMAND)|CONVERT\s+((INVERTED-)?DATE|TIME|TIME\s+STAMP|TEXT)|(CLOSE|OPEN)\s+(DATASET|CURSOR)|(TO|FROM)\s+(DATA BUFFER|INTERNAL TABLE|MEMORY ID|DATABASE|SHARED\s+(MEMORY|BUFFER))|DESCRIBE\s+(DISTANCE\s+BETWEEN|FIELD|LIST|TABLE)|FREE\s(MEMORY|OBJECT)?|PROCESS\s+(BEFORE\s+OUTPUT|AFTER\s+INPUT|ON\s+(VALUE-REQUEST|HELP-REQUEST))|AT\s+(LINE-SELECTION|USER-COMMAND|END\s+OF|NEW)|AT\s+SELECTION-SCREEN(\s+(ON(\s+(BLOCK|(HELP|VALUE)-REQUEST\s+FOR|END\s+OF|RADIOBUTTON\s+GROUP))?|OUTPUT))?|SELECTION-SCREEN:?\s+((BEGIN|END)\s+OF\s+((TABBED\s+)?BLOCK|LINE|SCREEN)|COMMENT|FUNCTION\s+KEY|INCLUDE\s+BLOCKS|POSITION|PUSHBUTTON|SKIP|ULINE)|LEAVE\s+(LIST-PROCESSING|PROGRAM|SCREEN|TO LIST-PROCESSING|TO TRANSACTION)(ENDING|STARTING)\s+AT|FORMAT\s+(COLOR|INTENSIFIED|INVERSE|HOTSPOT|INPUT|FRAMES|RESET)|AS\s+(CHECKBOX|SUBSCREEN|WINDOW)|WITH\s+(((NON-)?UNIQUE)?\s+KEY|FRAME)|(BEGIN|END)\s+OF|DELETE(\s+ADJACENT\s+DUPLICATES\sFROM)?|COMPARING(\s+ALL\s+FIELDS)?|(INSERT|APPEND)(\s+INITIAL\s+LINE\s+(IN)?TO|\s+LINES\s+OF)?|IN\s+((BYTE|CHARACTER)\s+MODE|PROGRAM)|END-OF-(DEFINITION|PAGE|SELECTION)|WITH\s+FRAME(\s+TITLE)|(REPLACE|FIND)\s+((FIRST|ALL)\s+OCCURRENCES?\s+OF\s+)?(SUBSTRING|REGEX)?|MATCH\s+(LENGTH|COUNT|LINE|OFFSET)|(RESPECTING|IGNORING)\s+CASE|IN\s+UPDATE\s+TASK|(SOURCE|RESULT)\s+(XML)?|REFERENCE\s+INTO|AND\s+(MARK|RETURN)|CLIENT\s+SPECIFIED|CORRESPONDING\s+FIELDS\s+OF|IF\s+FOUND|FOR\s+EVENT|INHERITING\s+FROM|LEAVE\s+TO\s+SCREEN|LOOP\s+AT\s+(SCREEN)?|LOWER\s+CASE|MATCHCODE\s+OBJECT|MODIF\s+ID|MODIFY\s+SCREEN|NESTING\s+LEVEL|NO\s+INTERVALS|OF\s+STRUCTURE|RADIOBUTTON\s+GROUP|RANGE\s+OF|REF\s+TO|SUPPRESS DIALOG|TABLE\s+OF|UPPER\s+CASE|TRANSPORTING\s+NO\s+FIELDS|VALUE\s+CHECK|VISIBLE\s+LENGTH|HEADER\s+LINE|COMMON\s+PART)\b`, Keyword, nil},
|
|
||||||
{`(^|(?<=(\s|\.)))(ABBREVIATED|ABSTRACT|ADD|ALIASES|ALIGN|ALPHA|ASSERT|AS|ASSIGN(ING)?|AT(\s+FIRST)?|BACK|BLOCK|BREAK-POINT|CASE|CATCH|CHANGING|CHECK|CLASS|CLEAR|COLLECT|COLOR|COMMIT|CREATE|COMMUNICATION|COMPONENTS?|COMPUTE|CONCATENATE|CONDENSE|CONSTANTS|CONTEXTS|CONTINUE|CONTROLS|COUNTRY|CURRENCY|DATA|DATE|DECIMALS|DEFAULT|DEFINE|DEFINITION|DEFERRED|DEMAND|DETAIL|DIRECTORY|DIVIDE|DO|DUMMY|ELSE(IF)?|ENDAT|ENDCASE|ENDCATCH|ENDCLASS|ENDDO|ENDFORM|ENDFUNCTION|ENDIF|ENDINTERFACE|ENDLOOP|ENDMETHOD|ENDMODULE|ENDSELECT|ENDTRY|ENDWHILE|ENHANCEMENT|EVENTS|EXACT|EXCEPTIONS?|EXIT|EXPONENT|EXPORT|EXPORTING|EXTRACT|FETCH|FIELDS?|FOR|FORM|FORMAT|FREE|FROM|FUNCTION|HIDE|ID|IF|IMPORT|IMPLEMENTATION|IMPORTING|IN|INCLUDE|INCLUDING|INDEX|INFOTYPES|INITIALIZATION|INTERFACE|INTERFACES|INTO|LANGUAGE|LEAVE|LENGTH|LINES|LOAD|LOCAL|JOIN|KEY|NEXT|MAXIMUM|MESSAGE|METHOD[S]?|MINIMUM|MODULE|MODIFIER|MODIFY|MOVE|MULTIPLY|NODES|NUMBER|OBLIGATORY|OBJECT|OF|OFF|ON|OTHERS|OVERLAY|PACK|PAD|PARAMETERS|PERCENTAGE|POSITION|PROGRAM|PROVIDE|PUBLIC|PUT|PF\d\d|RAISE|RAISING|RANGES?|READ|RECEIVE|REDEFINITION|REFRESH|REJECT|REPORT|RESERVE|RESUME|RETRY|RETURN|RETURNING|RIGHT|ROLLBACK|REPLACE|SCROLL|SEARCH|SELECT|SHIFT|SIGN|SINGLE|SIZE|SKIP|SORT|SPLIT|STATICS|STOP|STYLE|SUBMATCHES|SUBMIT|SUBTRACT|SUM(?!\()|SUMMARY|SUMMING|SUPPLY|TABLE|TABLES|TIMESTAMP|TIMES?|TIMEZONE|TITLE|\??TO|TOP-OF-PAGE|TRANSFER|TRANSLATE|TRY|TYPES|ULINE|UNDER|UNPACK|UPDATE|USING|VALUE|VALUES|VIA|VARYING|VARY|WAIT|WHEN|WHERE|WIDTH|WHILE|WITH|WINDOW|WRITE|XSD|ZERO)\b`, Keyword, nil},
|
|
||||||
{`(abs|acos|asin|atan|boolc|boolx|bit_set|char_off|charlen|ceil|cmax|cmin|condense|contains|contains_any_of|contains_any_not_of|concat_lines_of|cos|cosh|count|count_any_of|count_any_not_of|dbmaxlen|distance|escape|exp|find|find_end|find_any_of|find_any_not_of|floor|frac|from_mixed|insert|lines|log|log10|match|matches|nmax|nmin|numofchar|repeat|replace|rescale|reverse|round|segment|shift_left|shift_right|sign|sin|sinh|sqrt|strlen|substring|substring_after|substring_from|substring_before|substring_to|tan|tanh|to_upper|to_lower|to_mixed|translate|trunc|xstrlen)(\()\b`, ByGroups(NameBuiltin, Punctuation), nil},
|
|
||||||
{`&[0-9]`, Name, nil},
|
|
||||||
{`[0-9]+`, LiteralNumberInteger, nil},
|
|
||||||
{`(?<=(\s|.))(AND|OR|EQ|NE|GT|LT|GE|LE|CO|CN|CA|NA|CS|NOT|NS|CP|NP|BYTE-CO|BYTE-CN|BYTE-CA|BYTE-NA|BYTE-CS|BYTE-NS|IS\s+(NOT\s+)?(INITIAL|ASSIGNED|REQUESTED|BOUND))\b`, OperatorWord, nil},
|
|
||||||
Include("variable-names"),
|
|
||||||
{`[?*<>=\-+&]`, Operator, nil},
|
|
||||||
{`'(''|[^'])*'`, LiteralStringSingle, nil},
|
|
||||||
{"`([^`])*`", LiteralStringSingle, nil},
|
|
||||||
{`([|}])([^{}|]*?)([|{])`, ByGroups(Punctuation, LiteralStringSingle, Punctuation), nil},
|
|
||||||
{`[/;:()\[\],.]`, Punctuation, nil},
|
|
||||||
{`(!)(\w+)`, ByGroups(Operator, Name), nil},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-42
@@ -1,42 +0,0 @@
|
|||||||
package a
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/alecthomas/chroma" // nolint
|
|
||||||
"github.com/alecthomas/chroma/lexers/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Abnf lexer.
|
|
||||||
var Abnf = internal.Register(MustNewLazyLexer(
|
|
||||||
&Config{
|
|
||||||
Name: "ABNF",
|
|
||||||
Aliases: []string{"abnf"},
|
|
||||||
Filenames: []string{"*.abnf"},
|
|
||||||
MimeTypes: []string{"text/x-abnf"},
|
|
||||||
},
|
|
||||||
abnfRules,
|
|
||||||
))
|
|
||||||
|
|
||||||
func abnfRules() Rules {
|
|
||||||
return Rules{
|
|
||||||
"root": {
|
|
||||||
{`;.*$`, CommentSingle, nil},
|
|
||||||
{`(%[si])?"[^"]*"`, Literal, nil},
|
|
||||||
{`%b[01]+\-[01]+\b`, Literal, nil},
|
|
||||||
{`%b[01]+(\.[01]+)*\b`, Literal, nil},
|
|
||||||
{`%d[0-9]+\-[0-9]+\b`, Literal, nil},
|
|
||||||
{`%d[0-9]+(\.[0-9]+)*\b`, Literal, nil},
|
|
||||||
{`%x[0-9a-fA-F]+\-[0-9a-fA-F]+\b`, Literal, nil},
|
|
||||||
{`%x[0-9a-fA-F]+(\.[0-9a-fA-F]+)*\b`, Literal, nil},
|
|
||||||
{`\b[0-9]+\*[0-9]+`, Operator, nil},
|
|
||||||
{`\b[0-9]+\*`, Operator, nil},
|
|
||||||
{`\b[0-9]+`, Operator, nil},
|
|
||||||
{`\*`, Operator, nil},
|
|
||||||
{Words(``, `\b`, `ALPHA`, `BIT`, `CHAR`, `CR`, `CRLF`, `CTL`, `DIGIT`, `DQUOTE`, `HEXDIG`, `HTAB`, `LF`, `LWSP`, `OCTET`, `SP`, `VCHAR`, `WSP`), Keyword, nil},
|
|
||||||
{`[a-zA-Z][a-zA-Z0-9-]+\b`, NameClass, nil},
|
|
||||||
{`(=/|=|/)`, Operator, nil},
|
|
||||||
{`[\[\]()]`, Punctuation, nil},
|
|
||||||
{`\s+`, Text, nil},
|
|
||||||
{`.`, Text, nil},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-43
@@ -1,43 +0,0 @@
|
|||||||
package a
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/alecthomas/chroma" // nolint
|
|
||||||
"github.com/alecthomas/chroma/lexers/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Actionscript lexer.
|
|
||||||
var Actionscript = internal.Register(MustNewLazyLexer(
|
|
||||||
&Config{
|
|
||||||
Name: "ActionScript",
|
|
||||||
Aliases: []string{"as", "actionscript"},
|
|
||||||
Filenames: []string{"*.as"},
|
|
||||||
MimeTypes: []string{"application/x-actionscript", "text/x-actionscript", "text/actionscript"},
|
|
||||||
NotMultiline: true,
|
|
||||||
DotAll: true,
|
|
||||||
},
|
|
||||||
actionscriptRules,
|
|
||||||
))
|
|
||||||
|
|
||||||
func actionscriptRules() Rules {
|
|
||||||
return Rules{
|
|
||||||
"root": {
|
|
||||||
{`\s+`, Text, nil},
|
|
||||||
{`//.*?\n`, CommentSingle, nil},
|
|
||||||
{`/\*.*?\*/`, CommentMultiline, nil},
|
|
||||||
{`/(\\\\|\\/|[^/\n])*/[gim]*`, LiteralStringRegex, nil},
|
|
||||||
{`[~^*!%&<>|+=:;,/?\\-]+`, Operator, nil},
|
|
||||||
{`[{}\[\]();.]+`, Punctuation, nil},
|
|
||||||
{Words(``, `\b`, `case`, `default`, `for`, `each`, `in`, `while`, `do`, `break`, `return`, `continue`, `if`, `else`, `throw`, `try`, `catch`, `var`, `with`, `new`, `typeof`, `arguments`, `instanceof`, `this`, `switch`), Keyword, nil},
|
|
||||||
{Words(``, `\b`, `class`, `public`, `final`, `internal`, `native`, `override`, `private`, `protected`, `static`, `import`, `extends`, `implements`, `interface`, `intrinsic`, `return`, `super`, `dynamic`, `function`, `const`, `get`, `namespace`, `package`, `set`), KeywordDeclaration, nil},
|
|
||||||
{`(true|false|null|NaN|Infinity|-Infinity|undefined|Void)\b`, KeywordConstant, nil},
|
|
||||||
{Words(``, `\b`, `Accessibility`, `AccessibilityProperties`, `ActionScriptVersion`, `ActivityEvent`, `AntiAliasType`, `ApplicationDomain`, `AsBroadcaster`, `Array`, `AsyncErrorEvent`, `AVM1Movie`, `BevelFilter`, `Bitmap`, `BitmapData`, `BitmapDataChannel`, `BitmapFilter`, `BitmapFilterQuality`, `BitmapFilterType`, `BlendMode`, `BlurFilter`, `Boolean`, `ByteArray`, `Camera`, `Capabilities`, `CapsStyle`, `Class`, `Color`, `ColorMatrixFilter`, `ColorTransform`, `ContextMenu`, `ContextMenuBuiltInItems`, `ContextMenuEvent`, `ContextMenuItem`, `ConvultionFilter`, `CSMSettings`, `DataEvent`, `Date`, `DefinitionError`, `DeleteObjectSample`, `Dictionary`, `DisplacmentMapFilter`, `DisplayObject`, `DisplacmentMapFilterMode`, `DisplayObjectContainer`, `DropShadowFilter`, `Endian`, `EOFError`, `Error`, `ErrorEvent`, `EvalError`, `Event`, `EventDispatcher`, `EventPhase`, `ExternalInterface`, `FileFilter`, `FileReference`, `FileReferenceList`, `FocusDirection`, `FocusEvent`, `Font`, `FontStyle`, `FontType`, `FrameLabel`, `FullScreenEvent`, `Function`, `GlowFilter`, `GradientBevelFilter`, `GradientGlowFilter`, `GradientType`, `Graphics`, `GridFitType`, `HTTPStatusEvent`, `IBitmapDrawable`, `ID3Info`, `IDataInput`, `IDataOutput`, `IDynamicPropertyOutputIDynamicPropertyWriter`, `IEventDispatcher`, `IExternalizable`, `IllegalOperationError`, `IME`, `IMEConversionMode`, `IMEEvent`, `int`, `InteractiveObject`, `InterpolationMethod`, `InvalidSWFError`, `InvokeEvent`, `IOError`, `IOErrorEvent`, `JointStyle`, `Key`, `Keyboard`, `KeyboardEvent`, `KeyLocation`, `LineScaleMode`, `Loader`, `LoaderContext`, `LoaderInfo`, `LoadVars`, `LocalConnection`, `Locale`, `Math`, `Matrix`, `MemoryError`, `Microphone`, `MorphShape`, `Mouse`, `MouseEvent`, `MovieClip`, `MovieClipLoader`, `Namespace`, `NetConnection`, `NetStatusEvent`, `NetStream`, `NewObjectSample`, `Number`, `Object`, `ObjectEncoding`, `PixelSnapping`, `Point`, `PrintJob`, `PrintJobOptions`, `PrintJobOrientation`, `ProgressEvent`, `Proxy`, `QName`, `RangeError`, `Rectangle`, `ReferenceError`, `RegExp`, `Responder`, `Sample`, `Scene`, `ScriptTimeoutError`, `Security`, `SecurityDomain`, `SecurityError`, `SecurityErrorEvent`, `SecurityPanel`, `Selection`, `Shape`, `SharedObject`, `SharedObjectFlushStatus`, `SimpleButton`, `Socket`, `Sound`, `SoundChannel`, `SoundLoaderContext`, `SoundMixer`, `SoundTransform`, `SpreadMethod`, `Sprite`, `StackFrame`, `StackOverflowError`, `Stage`, `StageAlign`, `StageDisplayState`, `StageQuality`, `StageScaleMode`, `StaticText`, `StatusEvent`, `String`, `StyleSheet`, `SWFVersion`, `SyncEvent`, `SyntaxError`, `System`, `TextColorType`, `TextField`, `TextFieldAutoSize`, `TextFieldType`, `TextFormat`, `TextFormatAlign`, `TextLineMetrics`, `TextRenderer`, `TextSnapshot`, `Timer`, `TimerEvent`, `Transform`, `TypeError`, `uint`, `URIError`, `URLLoader`, `URLLoaderDataFormat`, `URLRequest`, `URLRequestHeader`, `URLRequestMethod`, `URLStream`, `URLVariabeles`, `VerifyError`, `Video`, `XML`, `XMLDocument`, `XMLList`, `XMLNode`, `XMLNodeType`, `XMLSocket`, `XMLUI`), NameBuiltin, nil},
|
|
||||||
{Words(``, `\b`, `decodeURI`, `decodeURIComponent`, `encodeURI`, `escape`, `eval`, `isFinite`, `isNaN`, `isXMLName`, `clearInterval`, `fscommand`, `getTimer`, `getURL`, `getVersion`, `parseFloat`, `parseInt`, `setInterval`, `trace`, `updateAfterEvent`, `unescape`), NameFunction, nil},
|
|
||||||
{`[$a-zA-Z_]\w*`, NameOther, nil},
|
|
||||||
{`[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?`, LiteralNumberFloat, nil},
|
|
||||||
{`0x[0-9a-f]+`, LiteralNumberHex, nil},
|
|
||||||
{`[0-9]+`, LiteralNumberInteger, nil},
|
|
||||||
{`"(\\\\|\\"|[^"])*"`, LiteralStringDouble, nil},
|
|
||||||
{`'(\\\\|\\'|[^'])*'`, LiteralStringSingle, nil},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-60
@@ -1,60 +0,0 @@
|
|||||||
package a
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/alecthomas/chroma" // nolint
|
|
||||||
"github.com/alecthomas/chroma/lexers/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Actionscript 3 lexer.
|
|
||||||
var Actionscript3 = internal.Register(MustNewLazyLexer(
|
|
||||||
&Config{
|
|
||||||
Name: "ActionScript 3",
|
|
||||||
Aliases: []string{"as3", "actionscript3"},
|
|
||||||
Filenames: []string{"*.as"},
|
|
||||||
MimeTypes: []string{"application/x-actionscript3", "text/x-actionscript3", "text/actionscript3"},
|
|
||||||
DotAll: true,
|
|
||||||
},
|
|
||||||
actionscript3Rules,
|
|
||||||
))
|
|
||||||
|
|
||||||
func actionscript3Rules() Rules {
|
|
||||||
return Rules{
|
|
||||||
"root": {
|
|
||||||
{`\s+`, Text, nil},
|
|
||||||
{`(function\s+)([$a-zA-Z_]\w*)(\s*)(\()`, ByGroups(KeywordDeclaration, NameFunction, Text, Operator), Push("funcparams")},
|
|
||||||
{`(var|const)(\s+)([$a-zA-Z_]\w*)(\s*)(:)(\s*)([$a-zA-Z_]\w*(?:\.<\w+>)?)`, ByGroups(KeywordDeclaration, Text, Name, Text, Punctuation, Text, KeywordType), nil},
|
|
||||||
{`(import|package)(\s+)((?:[$a-zA-Z_]\w*|\.)+)(\s*)`, ByGroups(Keyword, Text, NameNamespace, Text), nil},
|
|
||||||
{`(new)(\s+)([$a-zA-Z_]\w*(?:\.<\w+>)?)(\s*)(\()`, ByGroups(Keyword, Text, KeywordType, Text, Operator), nil},
|
|
||||||
{`//.*?\n`, CommentSingle, nil},
|
|
||||||
{`/\*.*?\*/`, CommentMultiline, nil},
|
|
||||||
{`/(\\\\|\\/|[^\n])*/[gisx]*`, LiteralStringRegex, nil},
|
|
||||||
{`(\.)([$a-zA-Z_]\w*)`, ByGroups(Operator, NameAttribute), nil},
|
|
||||||
{`(case|default|for|each|in|while|do|break|return|continue|if|else|throw|try|catch|with|new|typeof|arguments|instanceof|this|switch|import|include|as|is)\b`, Keyword, nil},
|
|
||||||
{`(class|public|final|internal|native|override|private|protected|static|import|extends|implements|interface|intrinsic|return|super|dynamic|function|const|get|namespace|package|set)\b`, KeywordDeclaration, nil},
|
|
||||||
{`(true|false|null|NaN|Infinity|-Infinity|undefined|void)\b`, KeywordConstant, nil},
|
|
||||||
{`(decodeURI|decodeURIComponent|encodeURI|escape|eval|isFinite|isNaN|isXMLName|clearInterval|fscommand|getTimer|getURL|getVersion|isFinite|parseFloat|parseInt|setInterval|trace|updateAfterEvent|unescape)\b`, NameFunction, nil},
|
|
||||||
{`[$a-zA-Z_]\w*`, Name, nil},
|
|
||||||
{`[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?`, LiteralNumberFloat, nil},
|
|
||||||
{`0x[0-9a-f]+`, LiteralNumberHex, nil},
|
|
||||||
{`[0-9]+`, LiteralNumberInteger, nil},
|
|
||||||
{`"(\\\\|\\"|[^"])*"`, LiteralStringDouble, nil},
|
|
||||||
{`'(\\\\|\\'|[^'])*'`, LiteralStringSingle, nil},
|
|
||||||
{`[~^*!%&<>|+=:;,/?\\{}\[\]().-]+`, Operator, nil},
|
|
||||||
},
|
|
||||||
"funcparams": {
|
|
||||||
{`\s+`, Text, nil},
|
|
||||||
{`(\s*)(\.\.\.)?([$a-zA-Z_]\w*)(\s*)(:)(\s*)([$a-zA-Z_]\w*(?:\.<\w+>)?|\*)(\s*)`, ByGroups(Text, Punctuation, Name, Text, Operator, Text, KeywordType, Text), Push("defval")},
|
|
||||||
{`\)`, Operator, Push("type")},
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
{`(\s*)(:)(\s*)([$a-zA-Z_]\w*(?:\.<\w+>)?|\*)`, ByGroups(Text, Operator, Text, KeywordType), Pop(2)},
|
|
||||||
{`\s+`, Text, Pop(2)},
|
|
||||||
Default(Pop(2)),
|
|
||||||
},
|
|
||||||
"defval": {
|
|
||||||
{`(=)(\s*)([^(),]+)(\s*)(,?)`, ByGroups(Operator, Text, UsingSelf("root"), Text, Operator), Pop(1)},
|
|
||||||
{`,`, Operator, Pop(1)},
|
|
||||||
Default(Pop(1)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-118
@@ -1,118 +0,0 @@
|
|||||||
package a
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/alecthomas/chroma" // nolint
|
|
||||||
"github.com/alecthomas/chroma/lexers/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ada lexer.
|
|
||||||
var Ada = internal.Register(MustNewLazyLexer(
|
|
||||||
&Config{
|
|
||||||
Name: "Ada",
|
|
||||||
Aliases: []string{"ada", "ada95", "ada2005"},
|
|
||||||
Filenames: []string{"*.adb", "*.ads", "*.ada"},
|
|
||||||
MimeTypes: []string{"text/x-ada"},
|
|
||||||
CaseInsensitive: true,
|
|
||||||
},
|
|
||||||
adaRules,
|
|
||||||
))
|
|
||||||
|
|
||||||
func adaRules() Rules {
|
|
||||||
return Rules{
|
|
||||||
"root": {
|
|
||||||
{`[^\S\n]+`, Text, nil},
|
|
||||||
{`--.*?\n`, CommentSingle, nil},
|
|
||||||
{`[^\S\n]+`, Text, nil},
|
|
||||||
{`function|procedure|entry`, KeywordDeclaration, Push("subprogram")},
|
|
||||||
{`(subtype|type)(\s+)(\w+)`, ByGroups(KeywordDeclaration, Text, KeywordType), Push("type_def")},
|
|
||||||
{`task|protected`, KeywordDeclaration, nil},
|
|
||||||
{`(subtype)(\s+)`, ByGroups(KeywordDeclaration, Text), nil},
|
|
||||||
{`(end)(\s+)`, ByGroups(KeywordReserved, Text), Push("end")},
|
|
||||||
{`(pragma)(\s+)(\w+)`, ByGroups(KeywordReserved, Text, CommentPreproc), nil},
|
|
||||||
{`(true|false|null)\b`, KeywordConstant, nil},
|
|
||||||
{Words(``, `\b`, `Address`, `Byte`, `Boolean`, `Character`, `Controlled`, `Count`, `Cursor`, `Duration`, `File_Mode`, `File_Type`, `Float`, `Generator`, `Integer`, `Long_Float`, `Long_Integer`, `Long_Long_Float`, `Long_Long_Integer`, `Natural`, `Positive`, `Reference_Type`, `Short_Float`, `Short_Integer`, `Short_Short_Float`, `Short_Short_Integer`, `String`, `Wide_Character`, `Wide_String`), KeywordType, nil},
|
|
||||||
{`(and(\s+then)?|in|mod|not|or(\s+else)|rem)\b`, OperatorWord, nil},
|
|
||||||
{`generic|private`, KeywordDeclaration, nil},
|
|
||||||
{`package`, KeywordDeclaration, Push("package")},
|
|
||||||
{`array\b`, KeywordReserved, Push("array_def")},
|
|
||||||
{`(with|use)(\s+)`, ByGroups(KeywordNamespace, Text), Push("import")},
|
|
||||||
{`(\w+)(\s*)(:)(\s*)(constant)`, ByGroups(NameConstant, Text, Punctuation, Text, KeywordReserved), nil},
|
|
||||||
{`<<\w+>>`, NameLabel, nil},
|
|
||||||
{`(\w+)(\s*)(:)(\s*)(declare|begin|loop|for|while)`, ByGroups(NameLabel, Text, Punctuation, Text, KeywordReserved), nil},
|
|
||||||
{Words(`\b`, `\b`, `abort`, `abs`, `abstract`, `accept`, `access`, `aliased`, `all`, `array`, `at`, `begin`, `body`, `case`, `constant`, `declare`, `delay`, `delta`, `digits`, `do`, `else`, `elsif`, `end`, `entry`, `exception`, `exit`, `interface`, `for`, `goto`, `if`, `is`, `limited`, `loop`, `new`, `null`, `of`, `or`, `others`, `out`, `overriding`, `pragma`, `protected`, `raise`, `range`, `record`, `renames`, `requeue`, `return`, `reverse`, `select`, `separate`, `subtype`, `synchronized`, `task`, `tagged`, `terminate`, `then`, `type`, `until`, `when`, `while`, `xor`), KeywordReserved, nil},
|
|
||||||
{`"[^"]*"`, LiteralString, nil},
|
|
||||||
Include("attribute"),
|
|
||||||
Include("numbers"),
|
|
||||||
{`'[^']'`, LiteralStringChar, nil},
|
|
||||||
{`(\w+)(\s*|[(,])`, ByGroups(Name, UsingSelf("root")), nil},
|
|
||||||
{`(<>|=>|:=|[()|:;,.'])`, Punctuation, nil},
|
|
||||||
{`[*<>+=/&-]`, Operator, nil},
|
|
||||||
{`\n+`, Text, nil},
|
|
||||||
},
|
|
||||||
"numbers": {
|
|
||||||
{`[0-9_]+#[0-9a-f]+#`, LiteralNumberHex, nil},
|
|
||||||
{`[0-9_]+\.[0-9_]*`, LiteralNumberFloat, nil},
|
|
||||||
{`[0-9_]+`, LiteralNumberInteger, nil},
|
|
||||||
},
|
|
||||||
"attribute": {
|
|
||||||
{`(')(\w+)`, ByGroups(Punctuation, NameAttribute), nil},
|
|
||||||
},
|
|
||||||
"subprogram": {
|
|
||||||
{`\(`, Punctuation, Push("#pop", "formal_part")},
|
|
||||||
{`;`, Punctuation, Pop(1)},
|
|
||||||
{`is\b`, KeywordReserved, Pop(1)},
|
|
||||||
{`"[^"]+"|\w+`, NameFunction, nil},
|
|
||||||
Include("root"),
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
{`(if|case|record|loop|select)`, KeywordReserved, nil},
|
|
||||||
{`"[^"]+"|[\w.]+`, NameFunction, nil},
|
|
||||||
{`\s+`, Text, nil},
|
|
||||||
{`;`, Punctuation, Pop(1)},
|
|
||||||
},
|
|
||||||
"type_def": {
|
|
||||||
{`;`, Punctuation, Pop(1)},
|
|
||||||
{`\(`, Punctuation, Push("formal_part")},
|
|
||||||
{`with|and|use`, KeywordReserved, nil},
|
|
||||||
{`array\b`, KeywordReserved, Push("#pop", "array_def")},
|
|
||||||
{`record\b`, KeywordReserved, Push("record_def")},
|
|
||||||
{`(null record)(;)`, ByGroups(KeywordReserved, Punctuation), Pop(1)},
|
|
||||||
Include("root"),
|
|
||||||
},
|
|
||||||
"array_def": {
|
|
||||||
{`;`, Punctuation, Pop(1)},
|
|
||||||
{`(\w+)(\s+)(range)`, ByGroups(KeywordType, Text, KeywordReserved), nil},
|
|
||||||
Include("root"),
|
|
||||||
},
|
|
||||||
"record_def": {
|
|
||||||
{`end record`, KeywordReserved, Pop(1)},
|
|
||||||
Include("root"),
|
|
||||||
},
|
|
||||||
"import": {
|
|
||||||
{`[\w.]+`, NameNamespace, Pop(1)},
|
|
||||||
Default(Pop(1)),
|
|
||||||
},
|
|
||||||
"formal_part": {
|
|
||||||
{`\)`, Punctuation, Pop(1)},
|
|
||||||
{`\w+`, NameVariable, nil},
|
|
||||||
{`,|:[^=]`, Punctuation, nil},
|
|
||||||
{`(in|not|null|out|access)\b`, KeywordReserved, nil},
|
|
||||||
Include("root"),
|
|
||||||
},
|
|
||||||
"package": {
|
|
||||||
{`body`, KeywordDeclaration, nil},
|
|
||||||
{`is\s+new|renames`, KeywordReserved, nil},
|
|
||||||
{`is`, KeywordReserved, Pop(1)},
|
|
||||||
{`;`, Punctuation, Pop(1)},
|
|
||||||
{`\(`, Punctuation, Push("package_instantiation")},
|
|
||||||
{`([\w.]+)`, NameClass, nil},
|
|
||||||
Include("root"),
|
|
||||||
},
|
|
||||||
"package_instantiation": {
|
|
||||||
{`("[^"]+"|\w+)(\s+)(=>)`, ByGroups(NameVariable, Text, Punctuation), nil},
|
|
||||||
{`[\w.\'"]`, Text, nil},
|
|
||||||
{`\)`, Punctuation, Pop(1)},
|
|
||||||
Include("root"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-47
@@ -1,47 +0,0 @@
|
|||||||
package a
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/alecthomas/chroma" // nolint
|
|
||||||
"github.com/alecthomas/chroma/lexers/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Al lexer.
|
|
||||||
var Al = internal.Register(MustNewLazyLexer(
|
|
||||||
&Config{
|
|
||||||
Name: "AL",
|
|
||||||
Aliases: []string{"al"},
|
|
||||||
Filenames: []string{"*.al", "*.dal"},
|
|
||||||
MimeTypes: []string{"text/x-al"},
|
|
||||||
DotAll: true,
|
|
||||||
CaseInsensitive: true,
|
|
||||||
},
|
|
||||||
alRules,
|
|
||||||
))
|
|
||||||
|
|
||||||
// https://github.com/microsoft/AL/blob/master/grammar/alsyntax.tmlanguage
|
|
||||||
func alRules() Rules {
|
|
||||||
return Rules{
|
|
||||||
"root": {
|
|
||||||
{`\s+`, TextWhitespace, nil},
|
|
||||||
{`(?s)\/\*.*?\\*\*\/`, CommentMultiline, nil},
|
|
||||||
{`(?s)//.*?\n`, CommentSingle, nil},
|
|
||||||
{`\"([^\"])*\"`, Text, nil},
|
|
||||||
{`'([^'])*'`, LiteralString, nil},
|
|
||||||
{`\b(?i:(ARRAY|ASSERTERROR|BEGIN|BREAK|CASE|DO|DOWNTO|ELSE|END|EVENT|EXIT|FOR|FOREACH|FUNCTION|IF|IMPLEMENTS|IN|INDATASET|INTERFACE|INTERNAL|LOCAL|OF|PROCEDURE|PROGRAM|PROTECTED|REPEAT|RUNONCLIENT|SECURITYFILTERING|SUPPRESSDISPOSE|TEMPORARY|THEN|TO|TRIGGER|UNTIL|VAR|WHILE|WITH|WITHEVENTS))\b`, Keyword, nil},
|
|
||||||
{`\b(?i:(AND|DIV|MOD|NOT|OR|XOR))\b`, OperatorWord, nil},
|
|
||||||
{`\b(?i:(AVERAGE|CONST|COUNT|EXIST|FIELD|FILTER|LOOKUP|MAX|MIN|ORDER|SORTING|SUM|TABLEDATA|UPPERLIMIT|WHERE|ASCENDING|DESCENDING))\b`, Keyword, nil},
|
|
||||||
{`\b(?i:(CODEUNIT|PAGE|PAGEEXTENSION|PAGECUSTOMIZATION|DOTNET|ENUM|ENUMEXTENSION|VALUE|QUERY|REPORT|TABLE|TABLEEXTENSION|XMLPORT|PROFILE|CONTROLADDIN|REPORTEXTENSION|INTERFACE|PERMISSIONSET|PERMISSIONSETEXTENSION|ENTITLEMENT))\b`, Keyword, nil},
|
|
||||||
{`\b(?i:(Action|Array|Automation|BigInteger|BigText|Blob|Boolean|Byte|Char|ClientType|Code|Codeunit|CompletionTriggerErrorLevel|ConnectionType|Database|DataClassification|DataScope|Date|DateFormula|DateTime|Decimal|DefaultLayout|Dialog|Dictionary|DotNet|DotNetAssembly|DotNetTypeDeclaration|Duration|Enum|ErrorInfo|ErrorType|ExecutionContext|ExecutionMode|FieldClass|FieldRef|FieldType|File|FilterPageBuilder|Guid|InStream|Integer|Joker|KeyRef|List|ModuleDependencyInfo|ModuleInfo|None|Notification|NotificationScope|ObjectType|Option|OutStream|Page|PageResult|Query|Record|RecordId|RecordRef|Report|ReportFormat|SecurityFilter|SecurityFiltering|Table|TableConnectionType|TableFilter|TestAction|TestField|TestFilterField|TestPage|TestPermissions|TestRequestPage|Text|TextBuilder|TextConst|TextEncoding|Time|TransactionModel|TransactionType|Variant|Verbosity|Version|XmlPort|HttpContent|HttpHeaders|HttpClient|HttpRequestMessage|HttpResponseMessage|JsonToken|JsonValue|JsonArray|JsonObject|View|Views|XmlAttribute|XmlAttributeCollection|XmlComment|XmlCData|XmlDeclaration|XmlDocument|XmlDocumentType|XmlElement|XmlNamespaceManager|XmlNameTable|XmlNode|XmlNodeList|XmlProcessingInstruction|XmlReadOptions|XmlText|XmlWriteOptions|WebServiceActionContext|WebServiceActionResultCode|SessionSettings))\b`, Keyword, nil},
|
|
||||||
{`\b([<>]=|<>|<|>)\b?`, Operator, nil},
|
|
||||||
{`\b(\-|\+|\/|\*)\b`, Operator, nil},
|
|
||||||
{`\s*(\:=|\+=|-=|\/=|\*=)\s*?`, Operator, nil},
|
|
||||||
{`\b(?i:(ADD|ADDFIRST|ADDLAST|ADDAFTER|ADDBEFORE|ACTION|ACTIONS|AREA|ASSEMBLY|CHARTPART|CUEGROUP|CUSTOMIZES|COLUMN|DATAITEM|DATASET|ELEMENTS|EXTENDS|FIELD|FIELDGROUP|FIELDATTRIBUTE|FIELDELEMENT|FIELDGROUPS|FIELDS|FILTER|FIXED|GRID|GROUP|MOVEAFTER|MOVEBEFORE|KEY|KEYS|LABEL|LABELS|LAYOUT|MODIFY|MOVEFIRST|MOVELAST|MOVEBEFORE|MOVEAFTER|PART|REPEATER|USERCONTROL|REQUESTPAGE|SCHEMA|SEPARATOR|SYSTEMPART|TABLEELEMENT|TEXTATTRIBUTE|TEXTELEMENT|TYPE))\b`, Keyword, nil},
|
|
||||||
{`\s*[(\.\.)&\|]\s*`, Operator, nil},
|
|
||||||
{`\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)(L|l|UL|ul|u|U|F|f|ll|LL|ull|ULL)?\b`, LiteralNumber, nil},
|
|
||||||
{`[;:,]`, Punctuation, nil},
|
|
||||||
{`#[ \t]*(if|else|elif|endif|define|undef|region|endregion|pragma)\b.*?\n`, CommentPreproc, nil},
|
|
||||||
{`\w+`, Text, nil},
|
|
||||||
{`.`, Text, nil},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-46
@@ -1,46 +0,0 @@
|
|||||||
package a
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/alecthomas/chroma" // nolint
|
|
||||||
"github.com/alecthomas/chroma/lexers/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Angular2 lexer.
|
|
||||||
var Angular2 = internal.Register(MustNewLazyLexer(
|
|
||||||
&Config{
|
|
||||||
Name: "Angular2",
|
|
||||||
Aliases: []string{"ng2"},
|
|
||||||
Filenames: []string{},
|
|
||||||
MimeTypes: []string{},
|
|
||||||
},
|
|
||||||
angular2Rules,
|
|
||||||
))
|
|
||||||
|
|
||||||
func angular2Rules() Rules {
|
|
||||||
return Rules{
|
|
||||||
"root": {
|
|
||||||
{`[^{([*#]+`, Other, nil},
|
|
||||||
{`(\{\{)(\s*)`, ByGroups(CommentPreproc, Text), Push("ngExpression")},
|
|
||||||
{`([([]+)([\w:.-]+)([\])]+)(\s*)(=)(\s*)`, ByGroups(Punctuation, NameAttribute, Punctuation, Text, Operator, Text), Push("attr")},
|
|
||||||
{`([([]+)([\w:.-]+)([\])]+)(\s*)`, ByGroups(Punctuation, NameAttribute, Punctuation, Text), nil},
|
|
||||||
{`([*#])([\w:.-]+)(\s*)(=)(\s*)`, ByGroups(Punctuation, NameAttribute, Punctuation, Operator), Push("attr")},
|
|
||||||
{`([*#])([\w:.-]+)(\s*)`, ByGroups(Punctuation, NameAttribute, Punctuation), nil},
|
|
||||||
},
|
|
||||||
"ngExpression": {
|
|
||||||
{`\s+(\|\s+)?`, Text, nil},
|
|
||||||
{`\}\}`, CommentPreproc, Pop(1)},
|
|
||||||
{`:?(true|false)`, LiteralStringBoolean, nil},
|
|
||||||
{`:?"(\\\\|\\"|[^"])*"`, LiteralStringDouble, nil},
|
|
||||||
{`:?'(\\\\|\\'|[^'])*'`, LiteralStringSingle, nil},
|
|
||||||
{`[0-9](\.[0-9]*)?(eE[+-][0-9])?[flFLdD]?|0[xX][0-9a-fA-F]+[Ll]?`, LiteralNumber, nil},
|
|
||||||
{`[a-zA-Z][\w-]*(\(.*\))?`, NameVariable, nil},
|
|
||||||
{`\.[\w-]+(\(.*\))?`, NameVariable, nil},
|
|
||||||
{`(\?)(\s*)([^}\s]+)(\s*)(:)(\s*)([^}\s]+)(\s*)`, ByGroups(Operator, Text, LiteralString, Text, Operator, Text, LiteralString, Text), nil},
|
|
||||||
},
|
|
||||||
"attr": {
|
|
||||||
{`".*?"`, LiteralString, Pop(1)},
|
|
||||||
{`'.*?'`, LiteralString, Pop(1)},
|
|
||||||
{`[^\s>]+`, LiteralString, Pop(1)},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-105
@@ -1,105 +0,0 @@
|
|||||||
package a
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/alecthomas/chroma" // nolint
|
|
||||||
"github.com/alecthomas/chroma/lexers/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ANTLR lexer.
|
|
||||||
var ANTLR = internal.Register(MustNewLazyLexer(
|
|
||||||
&Config{
|
|
||||||
Name: "ANTLR",
|
|
||||||
Aliases: []string{"antlr"},
|
|
||||||
Filenames: []string{},
|
|
||||||
MimeTypes: []string{},
|
|
||||||
},
|
|
||||||
antlrRules,
|
|
||||||
))
|
|
||||||
|
|
||||||
func antlrRules() Rules {
|
|
||||||
return Rules{
|
|
||||||
"whitespace": {
|
|
||||||
{`\s+`, TextWhitespace, nil},
|
|
||||||
},
|
|
||||||
"comments": {
|
|
||||||
{`//.*$`, Comment, nil},
|
|
||||||
{`/\*(.|\n)*?\*/`, Comment, nil},
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
Include("whitespace"),
|
|
||||||
Include("comments"),
|
|
||||||
{`(lexer|parser|tree)?(\s*)(grammar\b)(\s*)([A-Za-z]\w*)(;)`, ByGroups(Keyword, TextWhitespace, Keyword, TextWhitespace, NameClass, Punctuation), nil},
|
|
||||||
{`options\b`, Keyword, Push("options")},
|
|
||||||
{`tokens\b`, Keyword, Push("tokens")},
|
|
||||||
{`(scope)(\s*)([A-Za-z]\w*)(\s*)(\{)`, ByGroups(Keyword, TextWhitespace, NameVariable, TextWhitespace, Punctuation), Push("action")},
|
|
||||||
{`(catch|finally)\b`, Keyword, Push("exception")},
|
|
||||||
{`(@[A-Za-z]\w*)(\s*)(::)?(\s*)([A-Za-z]\w*)(\s*)(\{)`, ByGroups(NameLabel, TextWhitespace, Punctuation, TextWhitespace, NameLabel, TextWhitespace, Punctuation), Push("action")},
|
|
||||||
{`((?:protected|private|public|fragment)\b)?(\s*)([A-Za-z]\w*)(!)?`, ByGroups(Keyword, TextWhitespace, NameLabel, Punctuation), Push("rule-alts", "rule-prelims")},
|
|
||||||
},
|
|
||||||
"exception": {
|
|
||||||
{`\n`, TextWhitespace, Pop(1)},
|
|
||||||
{`\s`, TextWhitespace, nil},
|
|
||||||
Include("comments"),
|
|
||||||
{`\[`, Punctuation, Push("nested-arg-action")},
|
|
||||||
{`\{`, Punctuation, Push("action")},
|
|
||||||
},
|
|
||||||
"rule-prelims": {
|
|
||||||
Include("whitespace"),
|
|
||||||
Include("comments"),
|
|
||||||
{`returns\b`, Keyword, nil},
|
|
||||||
{`\[`, Punctuation, Push("nested-arg-action")},
|
|
||||||
{`\{`, Punctuation, Push("action")},
|
|
||||||
{`(throws)(\s+)([A-Za-z]\w*)`, ByGroups(Keyword, TextWhitespace, NameLabel), nil},
|
|
||||||
{`(,)(\s*)([A-Za-z]\w*)`, ByGroups(Punctuation, TextWhitespace, NameLabel), nil},
|
|
||||||
{`options\b`, Keyword, Push("options")},
|
|
||||||
{`(scope)(\s+)(\{)`, ByGroups(Keyword, TextWhitespace, Punctuation), Push("action")},
|
|
||||||
{`(scope)(\s+)([A-Za-z]\w*)(\s*)(;)`, ByGroups(Keyword, TextWhitespace, NameLabel, TextWhitespace, Punctuation), nil},
|
|
||||||
{`(@[A-Za-z]\w*)(\s*)(\{)`, ByGroups(NameLabel, TextWhitespace, Punctuation), Push("action")},
|
|
||||||
{`:`, Punctuation, Pop(1)},
|
|
||||||
},
|
|
||||||
"rule-alts": {
|
|
||||||
Include("whitespace"),
|
|
||||||
Include("comments"),
|
|
||||||
{`options\b`, Keyword, Push("options")},
|
|
||||||
{`:`, Punctuation, nil},
|
|
||||||
{`'(\\\\|\\'|[^'])*'`, LiteralString, nil},
|
|
||||||
{`"(\\\\|\\"|[^"])*"`, LiteralString, nil},
|
|
||||||
{`<<([^>]|>[^>])>>`, LiteralString, nil},
|
|
||||||
{`\$?[A-Z_]\w*`, NameConstant, nil},
|
|
||||||
{`\$?[a-z_]\w*`, NameVariable, nil},
|
|
||||||
{`(\+|\||->|=>|=|\(|\)|\.\.|\.|\?|\*|\^|!|\#|~)`, Operator, nil},
|
|
||||||
{`,`, Punctuation, nil},
|
|
||||||
{`\[`, Punctuation, Push("nested-arg-action")},
|
|
||||||
{`\{`, Punctuation, Push("action")},
|
|
||||||
{`;`, Punctuation, Pop(1)},
|
|
||||||
},
|
|
||||||
"tokens": {
|
|
||||||
Include("whitespace"),
|
|
||||||
Include("comments"),
|
|
||||||
{`\{`, Punctuation, nil},
|
|
||||||
{`([A-Z]\w*)(\s*)(=)?(\s*)(\'(?:\\\\|\\\'|[^\']*)\')?(\s*)(;)`, ByGroups(NameLabel, TextWhitespace, Punctuation, TextWhitespace, LiteralString, TextWhitespace, Punctuation), nil},
|
|
||||||
{`\}`, Punctuation, Pop(1)},
|
|
||||||
},
|
|
||||||
"options": {
|
|
||||||
Include("whitespace"),
|
|
||||||
Include("comments"),
|
|
||||||
{`\{`, Punctuation, nil},
|
|
||||||
{`([A-Za-z]\w*)(\s*)(=)(\s*)([A-Za-z]\w*|\'(?:\\\\|\\\'|[^\']*)\'|[0-9]+|\*)(\s*)(;)`, ByGroups(NameVariable, TextWhitespace, Punctuation, TextWhitespace, Text, TextWhitespace, Punctuation), nil},
|
|
||||||
{`\}`, Punctuation, Pop(1)},
|
|
||||||
},
|
|
||||||
"action": {
|
|
||||||
{`([^${}\'"/\\]+|"(\\\\|\\"|[^"])*"|'(\\\\|\\'|[^'])*'|//.*$\n?|/\*(.|\n)*?\*/|/(?!\*)(\\\\|\\/|[^/])*/|\\(?!%)|/)+`, Other, nil},
|
|
||||||
{`(\\)(%)`, ByGroups(Punctuation, Other), nil},
|
|
||||||
{`(\$[a-zA-Z]+)(\.?)(text|value)?`, ByGroups(NameVariable, Punctuation, NameProperty), nil},
|
|
||||||
{`\{`, Punctuation, Push()},
|
|
||||||
{`\}`, Punctuation, Pop(1)},
|
|
||||||
},
|
|
||||||
"nested-arg-action": {
|
|
||||||
{`([^$\[\]\'"/]+|"(\\\\|\\"|[^"])*"|'(\\\\|\\'|[^'])*'|//.*$\n?|/\*(.|\n)*?\*/|/(?!\*)(\\\\|\\/|[^/])*/|/)+`, Other, nil},
|
|
||||||
{`\[`, Punctuation, Push()},
|
|
||||||
{`\]`, Punctuation, Pop(1)},
|
|
||||||
{`(\$[a-zA-Z]+)(\.?)(text|value)?`, ByGroups(NameVariable, Punctuation, NameProperty), nil},
|
|
||||||
{`(\\\\|\\\]|\\\[|[^\[\]])+`, Other, nil},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-42
@@ -1,42 +0,0 @@
|
|||||||
package a
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/alecthomas/chroma" // nolint
|
|
||||||
"github.com/alecthomas/chroma/lexers/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Apacheconf lexer.
|
|
||||||
var Apacheconf = internal.Register(MustNewLazyLexer(
|
|
||||||
&Config{
|
|
||||||
Name: "ApacheConf",
|
|
||||||
Aliases: []string{"apacheconf", "aconf", "apache"},
|
|
||||||
Filenames: []string{".htaccess", "apache.conf", "apache2.conf"},
|
|
||||||
MimeTypes: []string{"text/x-apacheconf"},
|
|
||||||
CaseInsensitive: true,
|
|
||||||
},
|
|
||||||
apacheconfRules,
|
|
||||||
))
|
|
||||||
|
|
||||||
func apacheconfRules() Rules {
|
|
||||||
return Rules{
|
|
||||||
"root": {
|
|
||||||
{`\s+`, Text, nil},
|
|
||||||
{`(#.*?)$`, Comment, nil},
|
|
||||||
{`(<[^\s>]+)(?:(\s+)(.*?))?(>)`, ByGroups(NameTag, Text, LiteralString, NameTag), nil},
|
|
||||||
{`([a-z]\w*)(\s+)`, ByGroups(NameBuiltin, Text), Push("value")},
|
|
||||||
{`\.+`, Text, nil},
|
|
||||||
},
|
|
||||||
"value": {
|
|
||||||
{`\\\n`, Text, nil},
|
|
||||||
{`$`, Text, Pop(1)},
|
|
||||||
{`\\`, Text, nil},
|
|
||||||
{`[^\S\n]+`, Text, nil},
|
|
||||||
{`\d+\.\d+\.\d+\.\d+(?:/\d+)?`, LiteralNumber, nil},
|
|
||||||
{`\d+`, LiteralNumber, nil},
|
|
||||||
{`/([a-z0-9][\w./-]+)`, LiteralStringOther, nil},
|
|
||||||
{`(on|off|none|any|all|double|email|dns|min|minimal|os|productonly|full|emerg|alert|crit|error|warn|notice|info|debug|registry|script|inetd|standalone|user|group)\b`, Keyword, nil},
|
|
||||||
{`"([^"\\]*(?:\\.[^"\\]*)*)"`, LiteralStringDouble, nil},
|
|
||||||
{`[^\s"\\]+`, Text, nil},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user