diff --git a/.github/workflows/pr-receive.yml b/.github/workflows/pr-receive.yml index c8be38ea..316b58a5 100644 --- a/.github/workflows/pr-receive.yml +++ b/.github/workflows/pr-receive.yml @@ -16,6 +16,7 @@ jobs: run: | mkdir -p ./pr echo ${{ github.event.number }} > ./pr/NR + echo ${{ github.event.pull_request.head.sha }} > ./pr/SHA - uses: actions/upload-artifact@v2 with: name: pr diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index e9eb0472..98a0d6d2 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -7,10 +7,28 @@ on: workflows: ["Receive PR"] types: - completed +env: + YAO_DEV: ${{ github.WORKSPACE }} + YAO_ENV: development + YAO_ROOT: ${{ github.WORKSPACE }}/tests + YAO_HOST: 0.0.0.0 + YAO_PORT: 5099 + YAO_SESSION: "memory" + YAO_LOG: "./logs/application.log" + YAO_LOG_MODE: "TEXT" + YAO_JWT_SECRET: "bLp@bi!oqo-2U+hoTRUG" + YAO_DB_AESKEY: "ZLX=T&f6refeCh-ro*r@" + OSS_TEST_ID: ${{ secrets.OSS_TEST_ID}} + OSS_TEST_SECRET: ${{ secrets.OSS_TEST_SECRET}} + ROOT_PLUGIN: ${{ github.WORKSPACE }}/../../../data/gou-unit/plugins jobs: - upload: + UnitTest: runs-on: ubuntu-latest + strategy: + matrix: + go: [1.18.2] + db: [MySQL8.0, MySQL5.7, SQLite3] if: > ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }} @@ -35,21 +53,147 @@ jobs: }); var fs = require('fs'); fs.writeFileSync('${{github.workspace}}/pr.zip', Buffer.from(download.data)); - - name: "Unzip" + + - name: "Read NR & SHA" run: | unzip pr.zip - ls -l + cat NR + cat SHA + echo HEAD=$(cat SHA) >> $GITHUB_ENV + echo NR=$(cat NR) >> $GITHUB_ENV - name: "Comment on PR" uses: actions/github-script@v3 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | + const { NR } = process.env var fs = require('fs'); - var issue_number = Number(fs.readFileSync('./NR')); + var issue_number = NR; await github.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue_number, - body: 'Everything is OK. Thank you for the PR!' + body: 'Thank you for the PR! The ${{ matrix.db }} test workflow is running, the results of the run will be commented later.' + }); + + - name: Setup Cache + uses: actions/cache@v2 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Checkout Kun + uses: actions/checkout@v2 + with: + repository: yaoapp/kun + path: kun + + - name: Checkout Xun + uses: actions/checkout@v2 + with: + repository: yaoapp/xun + path: xun + + - name: Checkout Gou + uses: actions/checkout@v2 + with: + repository: yaoapp/gou + path: gou + + - name: Checkout V8Go + uses: actions/checkout@v2 + with: + repository: rogchap/v8go + ref: 5e91d3d9dcabd2986f901b6b31590e49fc3c4dd8 + path: v8go + + - name: Move Kun, Xun, Gou, V8Go + run: | + mv kun ../ + mv xun ../ + mv gou ../ + mv v8go ../ + ls -l . + ls -l ../ + + - name: Checkout pull request HEAD commit + uses: actions/checkout@v2 + with: + ref: ${{ env.HEAD }} + + - name: Setup ${{ matrix.db }} + uses: ./.github/actions/setup-db + with: + kind: "${{ matrix.db }}" + db: "xiang" + user: "xiang" + password: ${{ secrets.UNIT_PASS }} + + - name: Setup Go ${{ matrix.go }} + uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go }} + + - name: Setup Go Tools + run: | + make tools + + - name: Setup ENV & Host + env: + PASSWORD: ${{ secrets.UNIT_PASS }} + run: | + sudo echo "127.0.0.1 local.iqka.com" | sudo tee -a /etc/hosts + echo "YAO_DB_DRIVER=$DB_DRIVER" >> $GITHUB_ENV + echo "GITHUB_WORKSPACE:\n" && ls -l $GITHUB_WORKSPACE + + if [ "$DB_DRIVER" = "mysql" ]; then + echo "YAO_DB_PRIMARY=$DB_USER:$PASSWORD@$DB_HOST" >> $GITHUB_ENV + elif [ "$DB_DRIVER" = "postgres" ]; then + echo "YAO_DB_PRIMARY=postgres://$DB_USER:$PASSWORD@$DB_HOST" >> $GITHUB_ENV + else + echo "YAO_DB_PRIMARY=$YAO_ROOT/$DB_HOST" >> $GITHUB_ENV + fi + + echo ".:\n" && ls -l . + echo "..:\n" && ls -l .. + ping -c 1 -t 1 local.iqka.com + + - name: Test Prepare + run: | + make vet + make fmt-check + make misspell-check + make plugin + mkdir -p $YAO_ROOT/plugins/ + mv $ROOT_PLUGIN/user.so $YAO_ROOT/plugins/ + ls -l $YAO_ROOT/plugins/ + make migrate + + - name: Run test + run: | + make test + + - name: Codecov Report + uses: codecov/codecov-action@v2 + with: + token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos + + - name: "Comment on PR" + uses: actions/github-script@v3 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { NR } = process.env + var fs = require('fs'); + var issue_number = NR; + await github.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue_number, + body: '✨DONE✨ ${{ matrix.db }} passed.' }); diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index df49c894..6e5639d3 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -14,7 +14,7 @@ jobs: release: strategy: matrix: - go: [1.17] + go: [1.18.2] runs-on: "ubuntu-latest" steps: - name: Arm Build @@ -55,7 +55,6 @@ jobs: with: repository: yaoapp/gou path: gou - token: ${{ secrets.TOKEN }} - name: Checkout V8Go uses: actions/checkout@v2 @@ -69,7 +68,6 @@ jobs: with: repository: yaoapp/xgen path: ui - token: ${{ secrets.TOKEN }} - name: Move Kun, Xun, Gou, UI, V8Go run: | @@ -85,17 +83,13 @@ jobs: uses: actions/checkout@v2 - name: Setup Go ${{ matrix.go }} - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ matrix.go }} - name: Setup Go Tools run: | - if [[ "${GO111MODULE}" = "on" ]]; then go mod download; fi - if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi - if [[ "${GO111MODULE}" = "on" ]]; then make tools; fi - go get github.com/go-bindata/go-bindata/... - go get github.com/elazarl/go-bindata-assetfs/... + make tools - name: Make Artifacts Linux run: | @@ -114,15 +108,15 @@ jobs: run: | for file in ./dist/release/*; do coscmd upload $file /archives/; done; - - name: Configure COS For Beijing - env: - SECRET_ID: ${{ secrets.COS_ID }} - SECRET_KEY: ${{ secrets.COS_KEY }} - BUCKET: release-bj-1252011659 - REGION: ap-beijing - run: | - coscmd config -a $SECRET_ID -s $SECRET_KEY -b $BUCKET -r $REGION + # - name: Configure COS For Beijing + # env: + # SECRET_ID: ${{ secrets.COS_ID }} + # SECRET_KEY: ${{ secrets.COS_KEY }} + # BUCKET: release-bj-1252011659 + # REGION: ap-beijing + # run: | + # coscmd config -a $SECRET_ID -s $SECRET_KEY -b $BUCKET -r $REGION - - name: Push To Beijing - run: | - for file in ./dist/release/*; do coscmd upload $file /archives/; done; + # - name: Push To Beijing + # run: | + # for file in ./dist/release/*; do coscmd upload $file /archives/; done; diff --git a/.github/workflows/release-macos.yml b/.github/workflows/release-macos.yml index 8deb4586..daa2e376 100644 --- a/.github/workflows/release-macos.yml +++ b/.github/workflows/release-macos.yml @@ -14,7 +14,7 @@ jobs: release: strategy: matrix: - go: [1.17] + go: [1.18.2] runs-on: "macos-11" steps: - name: Install coscmd @@ -47,7 +47,6 @@ jobs: with: repository: yaoapp/gou path: gou - token: ${{ secrets.TOKEN }} - name: Checkout V8Go uses: actions/checkout@v2 @@ -61,7 +60,6 @@ jobs: with: repository: yaoapp/xgen path: ui - token: ${{ secrets.TOKEN }} - name: Move Kun, Xun, Gou, UI, V8Go run: | @@ -77,17 +75,13 @@ jobs: uses: actions/checkout@v2 - name: Setup Go ${{ matrix.go }} - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ matrix.go }} - name: Setup Go Tools run: | - if [[ "${GO111MODULE}" = "on" ]]; then go mod download; fi - if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi - if [[ "${GO111MODULE}" = "on" ]]; then make tools; fi - go get github.com/go-bindata/go-bindata/... - go get github.com/elazarl/go-bindata-assetfs/... + make tools - name: Make Artifacts MacOS run: | @@ -106,15 +100,15 @@ jobs: run: | for file in ./dist/release/*; do coscmd upload $file /archives/; done; - - name: Configure COS For Beijing - env: - SECRET_ID: ${{ secrets.COS_ID }} - SECRET_KEY: ${{ secrets.COS_KEY }} - BUCKET: release-bj-1252011659 - REGION: ap-beijing - run: | - coscmd config -a $SECRET_ID -s $SECRET_KEY -b $BUCKET -r $REGION + # - name: Configure COS For Beijing + # env: + # SECRET_ID: ${{ secrets.COS_ID }} + # SECRET_KEY: ${{ secrets.COS_KEY }} + # BUCKET: release-bj-1252011659 + # REGION: ap-beijing + # run: | + # coscmd config -a $SECRET_ID -s $SECRET_KEY -b $BUCKET -r $REGION - - name: Push To Beijing - run: | - for file in ./dist/release/*; do coscmd upload $file /archives/; done; + # - name: Push To Beijing + # run: | + # for file in ./dist/release/*; do coscmd upload $file /archives/; done; diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index e7987d1c..b5f507c7 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: [1.17] + go: [1.18.2] db: [MySQL8.0, MySQL5.7, SQLite3] steps: - name: Setup Cache @@ -80,22 +80,20 @@ jobs: kind: "${{ matrix.db }}" db: "xiang" user: "xiang" - password: 123456 + password: ${{ secrets.UNIT_PASS }} - name: Setup Go ${{ matrix.go }} - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ matrix.go }} - name: Setup Go Tools run: | - if [[ "${GO111MODULE}" = "on" ]]; then go mod download; fi - if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi - if [[ "${GO111MODULE}" = "on" ]]; then make tools; fi + make tools - name: Setup ENV & Host env: - PASSWORD: 123456 + PASSWORD: ${{ secrets.UNIT_PASS }} run: | sudo echo "127.0.0.1 local.iqka.com" | sudo tee -a /etc/hosts echo "YAO_DB_DRIVER=$DB_DRIVER" >> $GITHUB_ENV diff --git a/Makefile b/Makefile index 0d03f4d3..abcea79b 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,11 @@ GO ?= go +GIT ?= git GOFMT ?= gofmt "-s" PACKAGES ?= $(shell $(GO) list ./...) VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /examples/) GOFILES := $(shell find . -name "*.go") VERSION := $(shell grep 'const VERSION =' share/const.go |awk '{print $$4}' |sed 's/\"//g') +COMMIT := $(shell git log | head -n 1 | awk '{print substr($$2, 0, 12)}') # ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) TESTFOLDER := $(shell $(GO) list ./... | grep -vE 'examples|tests*|config') @@ -76,9 +78,10 @@ misspell: .PHONY: tools tools: - go install golang.org/x/lint/golint; \ - go install github.com/client9/misspell/cmd/misspell; - + go install golang.org/x/lint/golint@latest; \ + go install github.com/client9/misspell/cmd/misspell@latest; \ + go install github.com/go-bindata/go-bindata/...@latest; + # make plugin .PHONY: plugin plugin: @@ -130,6 +133,9 @@ artifacts-linux: clean rm -rf .tmp/data rm -rf .tmp/ui +# Replace PRVERSION + sed -ie "s/const PRVERSION = \"DEV\"/const PRVERSION = \"${COMMIT}\"/g" share/const.go + # Making artifacts mkdir -p dist CGO_ENABLED=1 CGO_LDFLAGS="-static" GOOS=linux GOARCH=amd64 go build -v -o dist/yao-${VERSION}-linux-amd64 @@ -141,6 +147,10 @@ artifacts-linux: clean ls -l dist/release/ dist/release/yao-${VERSION}-linux-amd64 version +# Reset const +# cp -f share/const.goe share/const.go +# rm -f share/const.goe + # make artifacts-macos .PHONY: artifacts-macos artifacts-macos: clean @@ -158,6 +168,9 @@ artifacts-macos: clean rm -rf .tmp/data rm -rf .tmp/ui +# Replace PRVERSION + sed -ie "s/const PRVERSION = \"DEV\"/const PRVERSION = \"${COMMIT}\"/g" share/const.go + # Making artifacts mkdir -p dist CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -v -o dist/yao-${VERSION}-darwin-amd64 @@ -169,6 +182,10 @@ artifacts-macos: clean ls -l dist/release/ dist/release/yao-${VERSION}-darwin-amd64 version +# Reset const +# cp -f share/const.goe share/const.go +# rm -f share/const.goe + .PHONY: debug debug: clean mkdir -p dist/release @@ -180,11 +197,19 @@ debug: clean go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/... rm -rf .tmp/data + +# Replace PRVERSION + sed -ie "s/const PRVERSION = \"DEV\"/const PRVERSION = \"${COMMIT}-debug\"/g" share/const.go + # Making artifacts mkdir -p dist CGO_ENABLED=1 go build -v -o dist/release/yao-debug chmod +x dist/release/yao-debug +# Reset const + cp -f share/const.goe share/const.go + rm -f share/const.goe + .PHONY: release release: clean mkdir -p dist/release @@ -193,7 +218,37 @@ release: clean # Building UI git clone https://github.com/YaoApp/xgen.git .tmp/ui sed -ie "s/url('\/icon/url('\/xiang\/icon/g" .tmp/ui/public/icon/md_icon.css - cd .tmp/ui && npm install && npm run build + cd .tmp/ui && cnpm install && npm run build + +# Packing + mkdir -p .tmp/data + cp -r .tmp/ui/dist .tmp/data/ui + cp -r yao .tmp/data/ + go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/... + rm -rf .tmp/data + rm -rf .tmp/ui + +# Replace PRVERSION + sed -ie "s/const PRVERSION = \"DEV\"/const PRVERSION = \"${COMMIT}\"/g" share/const.go + +# Making artifacts + mkdir -p dist + CGO_ENABLED=1 go build -v -o dist/release/yao + chmod +x dist/release/yao + +# Reset const + cp -f share/const.goe share/const.go + rm share/const.goe + +.PHONY: linux-release +linux-release: clean + mkdir -p dist/release + mkdir .tmp + +# Building UI + git clone https://github.com/YaoApp/xgen.git .tmp/ui + sed -ie "s/url('\/icon/url('\/xiang\/icon/g" .tmp/ui/public/icon/md_icon.css + cd .tmp/ui && yarn install && yarn build # Packing mkdir -p .tmp/data @@ -205,7 +260,7 @@ release: clean # Making artifacts mkdir -p dist - CGO_ENABLED=1 go build -v -o dist/release/yao + CGO_ENABLED=1 CGO_LDFLAGS="-static" go build -v -o dist/release/yao chmod +x dist/release/yao # make clean diff --git a/README.md b/README.md index fd093bc1..bc05aec5 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ Twitter · - - Slack + + Discord
@@ -35,17 +35,35 @@ - + [中文介绍](README.zh-CN.md) +YAO is an open-source application engine, written in Golang, in the form of a command-line tool that can be downloaded and used immediately. It is suitable for developing business systems, website/APP API, admin panel, self-built low-code platforms, etc. + +YAO adopts a flow-based programming model to implement various functions by writing YAO DSL (Logical Description in JSON format) or using JavaScript to write processes. The YAO DSL can be written in several ways: + +1. Purely hand-written + +2. Use automated scripts to generate contextual logic + +3. Use the visual editor to create by "drag and drop" + +**Discord:** https://discord.gg/nsKmCXwvxU + +**Documentation:** https://yaoapps.com/en-US/doc + ## Demo ### Customer Relationship Management System A Customer Relationship Management System developed by Yao. -[https://demo-crm.yaoapps.com](https://demo-crm.yaoapps.com/xiang/login/admin?autoLogin=true) +DEMO: [View Demo](https://demo-crm.yaoapps.com/xiang/login/admin?autoLogin=true) + +SOURCE: [https://github.com/YaoApp/demo-crm](https://github.com/YaoApp/demo-crm) + +INFRA: https://letsinfra.com/openapp/crm ### Intelligent warehouse management system @@ -55,7 +73,7 @@ An example of cloud + edge IoT application, an unattended intelligent warehouse ## Introduce -**Yao allows developers to create web services by processes.** Yao is a low-code engine that creates a database model, writes API services, and describes dashboard interface just by JSON for web & hardware, no code, and 10x productivity. +**Yao allows developers to create web services by processes.** Yao is a app engine that creates a database model, writes API services, and describes dashboard interface just by JSON for web & hardware, and 10x productivity. Yao is based on the **flow-based** programming idea, developed in the **Go** language, and supports multiple ways to expand the data stream processor. This makes Yao extremely versatile, which can replace programming languages in most scenarios, and is 10 times more efficient than traditional programming languages in terms of reusability and coding efficiency; application performance and resource ratio Better than **PHP**, **JAVA** and other languages. @@ -69,7 +87,7 @@ Run the script under terminal: (MacOS/Linux) curl -fsSL https://website.yaoapps.com/install.sh | bash ``` -For Windows users, please refer to the Installation and Debugging chapter: [Installation and debugging](https://yaoapps.com/en-US/doc/a.Introduction/b.Install) +For Windows users, please refer to the Installation and Debugging chapter: [Installation and debugging](https://yaoapps.com/en-US/doc/Introduction/Install) ## Getting Started @@ -121,4 +139,4 @@ yao start ## About Yao -Yao's name is derived from the Chinese character **yao (yáo)**, the basic symbol that makes up the Eight Trigrams. The Eight Trigrams is a symbol system created by the ancient god Fuxi after observing and summarizing the laws of nature, which can refer to everything. Yao has two states of yin and yang, like 0 and 1. The transformation of yin and yang of Yao drives the replacement of Eight Trigrams, so as to summarize and record the development law of things. +Yao's name is derived from the Chinese character **爻 (yáo)**, the basic symbol that makes up the Eight Trigrams. The Eight Trigrams is a symbol system created by the ancient god Fuxi after observing and summarizing the laws of nature, which can refer to everything. Yao has two states of yin and yang, like 0 and 1. The transformation of yin and yang of Yao drives the replacement of Eight Trigrams, so as to summarize and record the development law of things. diff --git a/README.zh-CN.md b/README.zh-CN.md index 207319c2..459402e8 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -3,33 +3,53 @@ [](https://github.com/YaoApp/yao/actions/workflows/unit-test.yml) [](https://codecov.io/gh/YaoApp/yao) - - -Yao 是一款支持快速创建 Web 服务和管理后台的开源低代码应用引擎。 + [English](README.md) +YAO 是一款开源应用引擎,使用 Golang 编写,以一个命令行工具的形式存在, 下载即用。适合用于开发业务系统、网站/APP API 接口、管理后台、自建低代码平台等。 + +YAO 采用 flow-based 的编程模式,通过编写 YAO DSL (JSON 格式逻辑描述) 或使用 JavaScript 编写处理器,实现各种功能。 YAO DSL 可以有多种编写方式: + +1. 纯手工编写 + +2. 使用自动化脚本,根据上下文逻辑生成 + +3. 使用可视化编辑器,通过“拖拉拽”制作 + 官网: [https://yaoapps.com](https://yaoapps.com) 文档: [https://yaoapps.com/doc](https://yaoapps.com/doc) +微信群: 添加微信 17192175099 备注(开发交流) + + + ## 演示 ### 客户管理系统 使用 Yao 搭建的一套通用 CRM 管理系统。 -[https://demo-crm.yaoapps.com](https://demo-crm.yaoapps.com/xiang/login/admin?autoLogin=true) +演示: [查看演示](https://demo-crm.yaoapps.com/xiang/login/admin?autoLogin=true) + +源码: https://github.com/YaoApp/demo-crm + +Infra 一键部署: https://letsinfra.com/openapp/crm ### 智能仓库管理系统 使用 Yao 搭建的云+边物联网应用,支持人脸识别、RFID 的无人值守智能仓库管理系统。 -[https://demo-wms.yaoapps.com](https://demo-crm.yaoapps.com/xiang/login/admin?autoLogin=true) +演示: [查看演示](https://demo-crm.yaoapps.com/xiang/login/admin?autoLogin=true) + +源码: https://github.com/YaoApp/yao-wms + +Infra 一键部署: https://letsinfra.com/openapp/wms ## 介绍 -Yao 是一个只需使用 JSON 即可创建数据库模型、编写 API 接口、描述管理后台界面的低代码引擎,使用 Yao 构建的应用可运行在云端或物联网设备上。 开发者不需要写一行代码,就可以拥有 10 倍生产力。 +Yao 是一个只需使用 JSON 即可创建数据库模型、编写 API 接口、描述管理后台界面的应用引擎,使用 Yao 构建的应用可运行在云端或物联网设备上。 开发者不需要写一行代码,就可以拥有 10 倍生产力。 Yao 基于 **flow-based** 编程思想,采用 **Go** 语言开发,支持多种方式扩展数据流处理器。这使得 Yao 具有极好的**通用性**,大部分场景下可以代替编程语言, 在复用性和编码效率上是传统编程语言的 **10 倍**;应用性能和资源占比上优于 **PHP**, **JAVA** 等语言。 diff --git a/api/api.go b/api/api.go index 5af1c70c..fb76b859 100644 --- a/api/api.go +++ b/api/api.go @@ -3,6 +3,7 @@ package api import ( "fmt" "path/filepath" + "strings" "github.com/yaoapp/gou" "github.com/yaoapp/kun/log" @@ -23,14 +24,33 @@ func LoadFrom(dir string, prefix string) error { if share.DirNotExists(dir) { return fmt.Errorf("%s does not exists", dir) } + + messages := []string{} err := share.Walk(dir, ".http.json", func(root, filename string) { name := prefix + share.SpecName(root, filename) content := share.ReadFile(filename) - _, err := gou.LoadAPIReturn(string(content), name) + _, err := gou.LoadAPIReturn(string(content), name, "bearer-jwt") if err != nil { log.With(log.F{"root": root, "file": filename}).Error(err.Error()) + messages = append(messages, fmt.Sprintf("%s %s", name, err.Error())) } }) + + // Load WebSocket Server + err = share.Walk(dir, ".ws.json", func(root, filename string) { + name := prefix + share.SpecName(root, filename) + content := share.ReadFile(filename) + _, err := gou.LoadWebSocketServer(string(content), name) + if err != nil { + log.With(log.F{"root": root, "file": filename}).Error(err.Error()) + messages = append(messages, fmt.Sprintf("%s %s", name, err.Error())) + } + }) + + if len(messages) > 0 { + return fmt.Errorf("%s", strings.Join(messages, ";")) + } + return err } diff --git a/api/api_test.go b/api/api_test.go index 1fcb35b3..8660f461 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/yaoapp/gou" + "github.com/yaoapp/gou/websocket" "github.com/yaoapp/yao/config" ) @@ -20,5 +21,12 @@ func check(t *testing.T) { for key := range gou.APIs { keys = append(keys, key) } - assert.Equal(t, 4, len(keys)) + + wskeys := []string{} + for key := range websocket.Upgraders { + wskeys = append(wskeys, key) + } + + assert.Equal(t, 5, len(keys)) + assert.Equal(t, 1, len(wskeys)) } diff --git a/app/app.go b/app/app.go index b9c2a83b..03333b8c 100644 --- a/app/app.go +++ b/app/app.go @@ -1,35 +1,32 @@ package app import ( - "fmt" - "os" - "path/filepath" - "strings" - jsoniter "github.com/json-iterator/go" + l "github.com/yaoapp/gou/lang" "github.com/yaoapp/kun/exception" - "github.com/yaoapp/kun/log" "github.com/yaoapp/kun/maps" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/data" + "github.com/yaoapp/yao/lang" "github.com/yaoapp/yao/share" "github.com/yaoapp/yao/xfs" ) -// langs 语言包 -var langs = map[string]map[string]string{} - -// Load 加载应用信息 +// Load Application func Load(cfg config.Config) { - Init(cfg) - LoadInfo(cfg.Root) - LoadLang(cfg) - lang := strings.ToLower(share.App.Lang) + // Load language packs + lang.Load(cfg) + + // Set language pack share.App.L = map[string]string{} - if l, has := langs[lang]; has { - share.App.L = l + if l.Default != nil { + share.App.L = l.Default.Global } + + // Load Info + LoadInfo(cfg.Root) + } // L 语言包 @@ -40,49 +37,6 @@ func L(word string) string { return word } -// Init 应用初始化 -func Init(cfg config.Config) { - - // // UI文件目录 - // if _, err := os.Stat(cfg.RootUI); os.IsNotExist(err) { - // err := os.MkdirAll(cfg.RootUI, os.ModePerm) - // if err != nil { - // xlog.Printf("创建目录失败(%s) %s", cfg.RootUI, err) - // os.Exit(1) - // } - - // content, err := data.Asset("xiang/data/index.html") - // if err != nil { - // xlog.Printf("读取文件失败(%s) %s", cfg.RootUI, err) - // os.Exit(1) - // } - - // err = ioutil.WriteFile(filepath.Join(cfg.RootUI, "/index.html"), content, os.ModePerm) - // if err != nil { - // xlog.Printf("复制默认文件失败(%s) %s", cfg.RootUI, err) - // os.Exit(1) - // } - // } - - // // 数据库目录 - // if _, err := os.Stat(cfg.RootDB); os.IsNotExist(err) { - // err := os.MkdirAll(cfg.RootDB, os.ModePerm) - // if err != nil { - // xlog.Printf("创建目录失败(%s) %s", cfg.RootDB, err) - // os.Exit(1) - // } - // } - - // // 文件数据目录 - // if _, err := os.Stat(cfg.RootData); os.IsNotExist(err) { - // err := os.MkdirAll(cfg.RootData, os.ModePerm) - // if err != nil { - // xlog.Printf("创建目录失败(%s) %s", cfg.RootData, err) - // os.Exit(1) - // } - // } -} - // LoadInfo 应用信息 func LoadInfo(root string) { info := defaultInfo() @@ -106,71 +60,15 @@ func LoadInfo(root string) { info.Icons.Set("png", xfs.Encode(fs.MustReadFile("/yao/icons/icon.png"))) } + info.L = share.App.L share.App = info } -// LoadLang 加载语言包 -func LoadLang(cfg config.Config) error { - - var defaults = []share.Script{} - if os.Getenv("YAO_DEV") != "" { - defaults = share.GetFilesFS(filepath.Join(os.Getenv("YAO_DEV"), "yao"), ".json") - } else { - defaults = share.GetFilesBin("yao", ".json") - } - - for _, lang := range defaults { - if lang.Type != "langs" { - continue - } - content := lang.Content - name := strings.ToLower(lang.Name) // 这个读取函数需要优化 - lang := map[string]string{} - err := jsoniter.Unmarshal(content, &lang) - if err != nil { - log.With(log.F{"name": name}).Error(err.Error()) - } - langs[name] = lang - } - - if share.BUILDIN { - return LoadLangBuildIn("langs") - } - return LoadLangFrom(filepath.Join(cfg.Root, "langs")) -} - // LoadLangBuildIn 从制品中读取 func LoadLangBuildIn(dir string) error { return nil } -// LoadLangFrom 从特定目录加载 -func LoadLangFrom(dir string) error { - - if share.DirNotExists(dir) { - return fmt.Errorf("%s does not exists", dir) - } - - err := share.Walk(dir, ".json", func(root, filename string) { - name := strings.ToLower(share.SpecName(root, filename)) - content := share.ReadFile(filename) - lang := map[string]string{} - err := jsoniter.Unmarshal(content, &lang) - if err != nil { - log.With(log.F{"root": root, "file": filename}).Error(err.Error()) - } - if _, has := langs[name]; !has { - langs[name] = map[string]string{} - } - for src, dst := range lang { - langs[name][src] = dst - } - }) - - return err - -} - // defaultInfo 读取默认应用信息 func defaultInfo() share.AppInfo { info := share.AppInfo{ diff --git a/app/app_test.go b/app/app_test.go index 05305342..32ea49bc 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -1,6 +1,7 @@ package app import ( + "os" "testing" "github.com/stretchr/testify/assert" @@ -9,7 +10,8 @@ import ( ) func TestLoad(t *testing.T) { + os.Setenv("YAO_LANG", "zh-cn") Load(config.Conf) - assert.Equal(t, "Yao", share.App.L["Yao"]) - assert.Equal(t, "Xiang", share.App.L["象传"]) + assert.Equal(t, "YAO", share.App.L["Yao"]) + assert.Equal(t, "象传", share.App.L["Xiang"]) } diff --git a/brain/README.md b/brain/README.md new file mode 100644 index 00000000..cc4ac020 --- /dev/null +++ b/brain/README.md @@ -0,0 +1,8 @@ +# Brain + +the robot who helps you build various types of applications + +the robot has two modes : + +1. Generate an App DSL based on the rules +2. Connect AI cloud to generate app DSL based on input ( Not Supported Yet ) diff --git a/brain/behaviors/listen/listen.go b/brain/behaviors/listen/listen.go new file mode 100644 index 00000000..7eb623ac --- /dev/null +++ b/brain/behaviors/listen/listen.go @@ -0,0 +1 @@ +package listen diff --git a/brain/behaviors/send/send.go b/brain/behaviors/send/send.go new file mode 100644 index 00000000..44dc5bad --- /dev/null +++ b/brain/behaviors/send/send.go @@ -0,0 +1 @@ +package send diff --git a/brain/behaviors/zouyu/README.md b/brain/behaviors/zouyu/README.md new file mode 100644 index 00000000..4eebd0f0 --- /dev/null +++ b/brain/behaviors/zouyu/README.md @@ -0,0 +1,7 @@ +# Zouyu + +Zouyu is a benevolent beast in ancient Chinese myths and legends. It is a very precious mythical beast in Lin's country. It is like a tiger, with colorful patterns on its body, and its tail is longer than its body. It looks like a tiger with white hair and black stripes, but it does not eat live animals. Travel thousands of miles a day. + +## Functions + +Connect a Database, generates a dashboard applications diff --git a/brain/behaviors/zouyu/types.go b/brain/behaviors/zouyu/types.go new file mode 100644 index 00000000..cc6aeb76 --- /dev/null +++ b/brain/behaviors/zouyu/types.go @@ -0,0 +1,3 @@ +package zouyu + +type conn struct{} diff --git a/brain/behaviors/zouyu/zouyu.go b/brain/behaviors/zouyu/zouyu.go new file mode 100644 index 00000000..7d3bc864 --- /dev/null +++ b/brain/behaviors/zouyu/zouyu.go @@ -0,0 +1,14 @@ +package zouyu + +// Run the data +func Run(dsn string) {} + +func (conn *conn) models() {} + +func (conn *conn) apis() {} + +func (conn *conn) flows() {} + +func (conn *conn) tables() {} + +func (conn *conn) env() {} diff --git a/brain/brain.go b/brain/brain.go new file mode 100644 index 00000000..02d20a87 --- /dev/null +++ b/brain/brain.go @@ -0,0 +1,18 @@ +package brain + +// NewBehaviors Returns the behaviors based on input words +func NewBehaviors(words string) (*Behaviors, error) { + resp, err := NPL(words) + if err != nil { + return nil, err + } + return resp.Behaviors, nil +} + +// Run the behaviors +func (behaviors *Behaviors) Run() {} + +// NPL the Natural language processing +func NPL(words string) (*Response, error) { + return &Response{Behaviors: &Behaviors{}}, nil +} diff --git a/brain/brain_test.go b/brain/brain_test.go new file mode 100644 index 00000000..480aea9e --- /dev/null +++ b/brain/brain_test.go @@ -0,0 +1,11 @@ +package brain + +import ( + "fmt" + "testing" +) + +func TestNewBehaviors(t *testing.T) { + behaviors, err := NewBehaviors("hello") + fmt.Println(behaviors, err) +} diff --git a/brain/cli/README.md b/brain/cli/README.md new file mode 100644 index 00000000..93c42785 --- /dev/null +++ b/brain/cli/README.md @@ -0,0 +1 @@ +# Command Line Interface diff --git a/brain/cli/cli.go b/brain/cli/cli.go new file mode 100644 index 00000000..7f1e458c --- /dev/null +++ b/brain/cli/cli.go @@ -0,0 +1 @@ +package cli diff --git a/brain/cloud/README.md b/brain/cloud/README.md new file mode 100644 index 00000000..91eec131 --- /dev/null +++ b/brain/cloud/README.md @@ -0,0 +1,3 @@ +# Cloud + +Connecting AI cloud to analyze behaviors diff --git a/brain/types.go b/brain/types.go new file mode 100644 index 00000000..d74eee79 --- /dev/null +++ b/brain/types.go @@ -0,0 +1,9 @@ +package brain + +// Behaviors the Behaviors struct +type Behaviors struct{} + +// Response NPL Response +type Response struct { + Behaviors *Behaviors +} diff --git a/brain/web/README.md b/brain/web/README.md new file mode 100644 index 00000000..42fc93e6 --- /dev/null +++ b/brain/web/README.md @@ -0,0 +1 @@ +# WEB Interface diff --git a/cert/cert.go b/cert/cert.go new file mode 100644 index 00000000..3ecffc3a --- /dev/null +++ b/cert/cert.go @@ -0,0 +1,35 @@ +package cert + +import ( + "fmt" + "path/filepath" + + "github.com/yaoapp/gou/ssl" + "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/share" +) + +// Load 加载API +func Load(cfg config.Config) error { + var root = filepath.Join(cfg.Root, "certs") + return LoadFrom(root, "") +} + +// LoadFrom 从特定目录加载 +func LoadFrom(dir string, prefix string) error { + + if share.DirNotExists(dir) { + return fmt.Errorf("%s does not exists", dir) + } + + err := share.Walk(dir, ".pem", func(root, filename string) { + name := prefix + share.SpecName(root, filename) + _, err := ssl.LoadCertificateFrom(filename, name) + if err != nil { + log.With(log.F{"root": root, "file": filename}).Error(err.Error()) + } + }) + + return err +} diff --git a/cert/cert_test.go b/cert/cert_test.go new file mode 100644 index 00000000..34cbdf23 --- /dev/null +++ b/cert/cert_test.go @@ -0,0 +1,46 @@ +package cert + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou" + "github.com/yaoapp/gou/ssl" + "github.com/yaoapp/yao/config" +) + +func TestLoad(t *testing.T) { + Load(config.Conf) + LoadFrom("not a path", "404.") + check(t) +} + +func TestProcessSign(t *testing.T) { + Load(config.Conf) + args := []interface{}{"hello world", "private", "SHA256"} + signature, err := gou.NewProcess("ssl.Sign", args...).Exec() + if err != nil { + t.Fatal(err) + } + assert.Equal(t, "EDHf3C9TXEk7y8LzIk5czLefXZyGxcMDVMcbNuBBegDkTqnPsRQnhFtNOgCdox8lI3MzLatwjoljoMY4Qk+sHGd5mAHMpiREa1gRFSVYpA2xvXZ3+KsfOHAdICQrfUdy59QaJGo6iGPNGG8PQOXHPTVNn6LMfryat9+f4l21DPAZiT0RyCUgFZE3/Qv8Z/6J4AsIXMSKZD6BGPPHUxGe7UBrXZvcR5dX25EiNjuH2OO38YJnDiTRVw14UI5fk/mQrwRdezj5tSKFCyHt912BZExXtkHISiYFNTZ/2RhOup5Xx6o3GvrEOdshrnN80Lwu1Aaju+lnZp13hDz4P6hU7w==", signature) +} + +func TestProcessVerify(t *testing.T) { + Load(config.Conf) + signature := "EDHf3C9TXEk7y8LzIk5czLefXZyGxcMDVMcbNuBBegDkTqnPsRQnhFtNOgCdox8lI3MzLatwjoljoMY4Qk+sHGd5mAHMpiREa1gRFSVYpA2xvXZ3+KsfOHAdICQrfUdy59QaJGo6iGPNGG8PQOXHPTVNn6LMfryat9+f4l21DPAZiT0RyCUgFZE3/Qv8Z/6J4AsIXMSKZD6BGPPHUxGe7UBrXZvcR5dX25EiNjuH2OO38YJnDiTRVw14UI5fk/mQrwRdezj5tSKFCyHt912BZExXtkHISiYFNTZ/2RhOup5Xx6o3GvrEOdshrnN80Lwu1Aaju+lnZp13hDz4P6hU7w==" + args := []interface{}{"hello world", signature, "cert", "SHA256"} + res, err := gou.NewProcess("ssl.Verify", args...).Exec() + if err != nil { + t.Fatal(err) + } + + assert.True(t, res.(bool)) +} + +func check(t *testing.T) { + keys := []string{} + for key := range ssl.Certificates { + keys = append(keys, key) + } + assert.Equal(t, 3, len(keys)) +} diff --git a/chart/chart.go b/chart/chart.go index 43da6e2e..e0714152 100644 --- a/chart/chart.go +++ b/chart/chart.go @@ -6,6 +6,7 @@ import ( jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou" + "github.com/yaoapp/gou/lang" "github.com/yaoapp/kun/exception" "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/config" @@ -65,9 +66,7 @@ func LoadFrom(dir string, prefix string) error { // LoadChart 载入数据表格 func LoadChart(source []byte, name string) (*Chart, error) { chart := &Chart{ - Flow: gou.Flow{ - Name: name, - }, + Flow: &gou.Flow{Name: name}, } err := jsoniter.Unmarshal(source, chart) if err != nil { @@ -77,6 +76,12 @@ func LoadChart(source []byte, name string) (*Chart, error) { chart.Prepare() chart.SetupAPIs() Charts[name] = chart + + // Apply a language pack + if lang.Default != nil { + lang.Default.Apply(Charts[name]) + } + return chart, nil } diff --git a/chart/chart_test.go b/chart/chart_test.go index 28f94b0d..150df114 100644 --- a/chart/chart_test.go +++ b/chart/chart_test.go @@ -1,16 +1,22 @@ package chart import ( + "os" "testing" "github.com/stretchr/testify/assert" "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/lang" "github.com/yaoapp/yao/model" "github.com/yaoapp/yao/query" "github.com/yaoapp/yao/share" ) func TestLoad(t *testing.T) { + + os.Setenv("YAO_LANG", "zh-hk") + lang.Load(config.Conf) + share.DBConnect(config.Conf.DB) model.Load(config.Conf) query.Load(config.Conf) @@ -25,5 +31,15 @@ func check(t *testing.T) { for key := range Charts { keys = append(keys, key) } - assert.Equal(t, 1, len(keys)) + assert.Equal(t, 3, len(keys)) + + lang := Charts["lang"] + output := lang.Output.(map[string]interface{}) + assert.Equal(t, "{{$in}}", output["參數"]) + + filters := lang.Page.Layout["filters"].([]interface{}) + begin := filters[0].(map[string]interface{}) + assert.Equal(t, "開始時間", begin["name"]) + + assert.Equal(t, "請選擇開始時間", lang.Filters["開始時間"].Input.Props["placeholder"]) } diff --git a/chart/lang.go b/chart/lang.go new file mode 100644 index 00000000..667f7407 --- /dev/null +++ b/chart/lang.go @@ -0,0 +1,125 @@ +package chart + +// Lang for applying a language pack +func (chart *Chart) Lang(trans func(widget string, inst string, value *string) bool) { + inst := chart.Flow.Name + widget := "chart" + + trans(widget, inst, &chart.Name) + trans(widget, inst, &chart.Label) + trans(widget, inst, &chart.Description) + trans(widget, inst, &chart.Page.Primary) + transMap(widget, inst, chart.Page.Layout, trans) + chart.Output = transAny(widget, inst, chart.Output, trans) + + // Filters + for name, filter := range chart.Filters { + new := name + trans(widget, inst, &new) + trans(widget, inst, &filter.Label) + delete(chart.Filters, name) + + // Props + transMap(widget, inst, filter.Input.Props, trans) + chart.Filters[new] = filter + } + +} + +func transAny(widget string, inst string, input interface{}, trans func(widget string, inst string, value *string) bool) interface{} { + switch input.(type) { + case []interface{}: + values := input.([]interface{}) + transArr(widget, inst, values, trans) + input = values + break + + case map[string]interface{}: + values := input.(map[string]interface{}) + for name, value := range values { + new := name + newValue := value + + switch value.(type) { + case string: + val := value.(string) + trans(widget, inst, &val) + newValue = val + break + + case []interface{}: + vals := value.([]interface{}) + transArr(widget, inst, vals, trans) + newValue = vals + break + + case map[string]interface{}: + vals := value.(map[string]interface{}) + transMap(widget, inst, vals, trans) + newValue = vals + break + } + + trans(widget, inst, &new) + delete(values, name) + values[new] = newValue + } + input = values + break + } + + return input +} + +func transMap(widget string, inst string, values map[string]interface{}, trans func(widget string, inst string, value *string) bool) { + for key, value := range values { + + switch value.(type) { + + case string: + v := value.(string) + trans(widget, inst, &v) + values[key] = v + break + + case []interface{}: + v := value.([]interface{}) + transArr(widget, inst, v, trans) + values[key] = v + break + + case map[string]interface{}: + v := value.(map[string]interface{}) + transMap(widget, inst, v, trans) + values[key] = v + break + } + + } +} + +func transArr(widget string, inst string, values []interface{}, trans func(widget string, inst string, value *string) bool) { + for key, value := range values { + switch value.(type) { + + case string: + v := value.(string) + trans(widget, inst, &v) + values[key] = v + break + + case []interface{}: + v := value.([]interface{}) + transArr(widget, inst, v, trans) + values[key] = v + break + + case map[string]interface{}: + v := value.(map[string]interface{}) + transMap(widget, inst, v, trans) + values[key] = v + break + } + + } +} diff --git a/chart/process.go b/chart/process.go index f3593edc..d6c73ebd 100644 --- a/chart/process.go +++ b/chart/process.go @@ -33,6 +33,10 @@ func ProcessData(process *gou.Process) interface{} { } } + // with Session + chart.Flow.WithGlobal(process.Global) + chart.Flow.WithSID(process.Sid) + return chart.GetData(params) } diff --git a/chart/process_test.go b/chart/process_test.go index 91067a35..9a7ed568 100644 --- a/chart/process_test.go +++ b/chart/process_test.go @@ -3,12 +3,13 @@ package chart import ( "net/url" "testing" + "time" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/yaoapp/gou" + "github.com/yaoapp/gou/session" "github.com/yaoapp/kun/any" - "github.com/yaoapp/kun/utils" "github.com/yaoapp/yao/config" _ "github.com/yaoapp/yao/helper" "github.com/yaoapp/yao/model" @@ -68,10 +69,36 @@ func TestProcessData(t *testing.T) { } process := gou.NewProcess("xiang.chart.Data", args...) response := ProcessData(process) - utils.Dump(response) + // utils.Dump(response) assert.NotNil(t, response) res := any.Of(response).Map().Dot() assert.Equal(t, "北京", res.Get("合并.0.城市")) } + +func TestProcessDataGlobalSession(t *testing.T) { + + params := url.Values{ + "from": []string{"1981-01-01", "1990-01-01"}, + } + params.Set("to", "2049-12-31") + + args := []interface{}{ + "session", + params, + &gin.Context{}, + } + + sid := session.ID() + data := time.Now().String() + session.Global().ID(sid).Set("id", data) + process := gou. + NewProcess("xiang.chart.Data", args...). + WithSID(sid). + WithGlobal(map[string]interface{}{"foo": "bar"}) + + response := ProcessData(process) + res := any.Of(response).Map().Dot() + assert.Equal(t, data, res.Get("ID")) +} diff --git a/chart/types.go b/chart/types.go index 446517fb..b7b7e588 100644 --- a/chart/types.go +++ b/chart/types.go @@ -7,7 +7,7 @@ import ( // Chart 图表格式 type Chart struct { - gou.Flow + *gou.Flow APIs map[string]share.API `json:"apis,omitempty"` Filters map[string]share.Filter `json:"filters,omitempty"` Page share.Page `json:"page,omitempty"` diff --git a/cmd/brain.go b/cmd/brain.go new file mode 100644 index 00000000..1d619dd0 --- /dev/null +++ b/cmd/brain.go @@ -0,0 +1 @@ +package cmd diff --git a/cmd/dump.go b/cmd/dump.go new file mode 100644 index 00000000..5c90b900 --- /dev/null +++ b/cmd/dump.go @@ -0,0 +1,205 @@ +package cmd + +import ( + "archive/zip" + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" + + "github.com/fatih/color" + "github.com/spf13/cobra" + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/exception" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/engine" +) + +var dumpModel string +var dumpCmd = &cobra.Command{ + Use: "dump", + Short: L("Dump the application data"), + Long: L("Dump the application data"), + Run: func(cmd *cobra.Command, args []string) { + defer func() { + err := exception.Catch(recover()) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + } + }() + + Boot() + + path, err := filepath.Abs(".") + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + output := filepath.Join(fmt.Sprintf("%s-%s.zip", filepath.Base(path), time.Now().Format("20060102150405"))) + if len(args) > 0 { + output = args[0] + } + + output, err = filepath.Abs(output) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + _, err = os.Stat(output) + if !errors.Is(err, os.ErrNotExist) { + fmt.Println(color.RedString("%s exists", output)) + os.Exit(1) + } + + // Load model + err = engine.Load(config.Conf) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + if dumpModel != "" { + fmt.Println(color.YellowString(L("Not supported yet"))) + os.Exit(1) + return + } + + // Export models + files := []string{} + for _, mod := range gou.Models { + + fmt.Printf("\r%s", color.GreenString(L("Export the models: %s (%s)"), mod.Name, mod.MetaData.Table.Name)) + jsonfiles, err := mod.Export(5000, func(curr, total int) { + fmt.Printf("\r%s", strings.Repeat(" ", 80)) + fmt.Printf("\r%s", color.GreenString(L("Export the models: %s (%s) %d/%d"), mod.Name, mod.MetaData.Table.Name, curr, total)) + }) + + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + files = append(files, jsonfiles...) + } + fmt.Printf("\r%s", strings.Repeat(" ", 80)) + fmt.Printf("\r%s\n", color.GreenString(L("Export the models: ✨DONE✨"))) + + // Compress files + err = zipfiles(files, output, func(file string) { + fmt.Printf("\r%s", strings.Repeat(" ", 80)) + fmt.Printf("\r%s", color.GreenString(L("Compress the files: %s"), file)) + }) + fmt.Printf("\r%s", strings.Repeat(" ", 80)) + fmt.Printf("\r%s\n", color.GreenString(L("Compress the files: ✨DONE✨"))) + + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + fmt.Println(color.GreenString("File: %s", output)) + }, +} + +// func init() { +// // dumpCmd.PersistentFlags().StringVarP(&dumpModel, "name", "n", "", L("Model name")) +// } + +// gzipfiles +func zipfiles(files []string, output string, process func(file string)) error { + outpath := filepath.Dir(output) + os.MkdirAll(outpath, 0755) + + outfile, err := os.Create(output) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + defer outfile.Close() + + w := zip.NewWriter(outfile) + defer func() { + w.Close() + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + }() + + for _, file := range files { + addFile(w, file, "model", process) + } + + // Add data path + dataPath := filepath.Join(config.Conf.Root, "data") + _, err = os.Stat(dataPath) + if err == nil { + addFolder(w, dataPath, "data", process) + } + + return nil +} + +func addFile(w *zip.Writer, file, baseInZip string, process func(file string)) { + process(filepath.Join(baseInZip, filepath.Base(file))) + dat, err := ioutil.ReadFile(file) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + // Add some files to the archive. + f, err := w.Create(filepath.Join(baseInZip, filepath.Base(file))) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + _, err = f.Write(dat) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + os.Remove(file) +} + +func addFolder(w *zip.Writer, basePath, baseInZip string, process func(file string)) { + + // Open the Directory + files, err := ioutil.ReadDir(basePath) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + for _, file := range files { + process(filepath.Join(baseInZip, file.Name())) + if !file.IsDir() { + dat, err := ioutil.ReadFile(filepath.Join(basePath, file.Name())) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + // Add some files to the archive. + f, err := w.Create(filepath.Join(baseInZip, file.Name())) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + _, err = f.Write(dat) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + } else if file.IsDir() { + // Recurse + newBase := filepath.Join(basePath, file.Name()) + addFolder(w, newBase, filepath.Join(baseInZip, file.Name()), process) + } + } +} diff --git a/cmd/init.go b/cmd/init.go index 6bcb5e9e..e160524d 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -301,7 +301,8 @@ func makeFile(name string, source string) { makeFileContent(name, []byte(source)) } -func makeFileContent(filename string, content []byte) { +func makeFileContent(name string, content []byte) { + filename := filepath.Join(config.Conf.Root, name) err := os.WriteFile(filename, content, 0644) if err != nil { fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) diff --git a/cmd/migrate.go b/cmd/migrate.go index a6ac9f73..1115a646 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -15,6 +15,7 @@ import ( var name string var force bool = false +var resetModel bool = false var migrateCmd = &cobra.Command{ Use: "migrate", Short: L("Update database schema"), @@ -43,23 +44,57 @@ var migrateCmd = &cobra.Command{ if name != "" { mod, has := gou.Models[name] - if has { - mod.Migrate(true) + fmt.Printf(color.WhiteString(L("Update schema model: %s (%s) "), mod.Name, mod.MetaData.Table.Name) + "\t") + + if !has { + fmt.Println(color.RedString(L("Model: %s does not exits"), name)) + return } + + if resetModel { + err := mod.DropTable() + if err != nil { + fmt.Printf(color.RedString(L("FAILURE\n%s"), err.Error()) + "\n") + return + } + } + + err := mod.Migrate(false) + if err != nil { + fmt.Printf(color.RedString(L("FAILURE\n%s"), err.Error()) + "\n") + return + } + + fmt.Printf(color.GreenString(L("SUCCESS")) + "\n") return } // Do Stuff Here for _, mod := range gou.Models { - fmt.Println(color.GreenString(L("Update schema model: %s (%s) "), mod.Name, mod.MetaData.Table.Name)) - mod.Migrate(true) + fmt.Printf(color.WhiteString(L("Update schema model: %s (%s) "), mod.Name, mod.MetaData.Table.Name) + "\t") + + if resetModel { + err := mod.DropTable() + if err != nil { + fmt.Printf(color.RedString(L("FAILURE\n%s"), err.Error()) + "\n") + continue + } + } + + err := mod.Migrate(false) + if err != nil { + fmt.Printf(color.RedString(L("FAILURE\n%s"), err.Error()) + "\n") + continue + } + fmt.Printf(color.GreenString(L("SUCCESS")) + "\n") } - fmt.Println(color.GreenString(L("✨DONE✨"))) + // fmt.Println(color.GreenString(L("✨DONE✨"))) }, } func init() { migrateCmd.PersistentFlags().StringVarP(&name, "name", "n", "", L("Model name")) migrateCmd.PersistentFlags().BoolVarP(&force, "force", "", false, L("Force migrate")) + migrateCmd.PersistentFlags().BoolVarP(&resetModel, "reset", "", false, L("Drop the table if exist")) } diff --git a/cmd/restore.go b/cmd/restore.go new file mode 100644 index 00000000..107f7e7b --- /dev/null +++ b/cmd/restore.go @@ -0,0 +1,206 @@ +package cmd + +import ( + "archive/zip" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" + + "github.com/fatih/color" + "github.com/spf13/cobra" + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/exception" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/engine" + "github.com/yaoapp/yao/share" +) + +var restoreForce bool = false +var restoreCmd = &cobra.Command{ + Use: "restore", + Short: L("Restore the application data"), + Long: L("Restore the application data"), + Run: func(cmd *cobra.Command, args []string) { + defer func() { + err := exception.Catch(recover()) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + } + }() + + if len(args) < 1 { + fmt.Println(color.RedString(L("Not enough arguments"))) + fmt.Println(color.WhiteString(share.BUILDNAME + " help")) + os.Exit(1) + } + + zipfile, err := filepath.Abs(args[0]) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + Boot() + + if !restoreForce && config.Conf.Mode == "production" { + fmt.Println(color.WhiteString(L("TRY:")), color.GreenString("%s restore --force", share.BUILDNAME)) + exception.New(L("Retore is not allowed on production mode."), 403).Throw() + } + + // Unzip files + dst := unzipFile(zipfile, func(file string) { + fmt.Printf("\r%s", strings.Repeat(" ", 80)) + fmt.Printf("\r%s", color.GreenString(L("Unzip the file: %s"), file)) + }) + + // 加载数据模型 + err = engine.Load(config.Conf) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + // Restore models + restoreModels(filepath.Join(dst, "model")) + + // Restore Data + restoreData(filepath.Join(dst, "data")) + + // Clean + os.RemoveAll(dst) + + fmt.Println(color.GreenString(L("✨DONE✨"))) + }, +} + +func init() { + restoreCmd.PersistentFlags().BoolVarP(&restoreForce, "force", "", false, L("Force restore")) +} + +func restoreData(basePath string) { + + _, err := os.Stat(basePath) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + // Clean Data + dataPath := filepath.Join(config.Conf.Root, "data") + _, err = os.Stat(dataPath) + if err == nil { + os.RemoveAll(dataPath) + } + + err = os.Rename(basePath, dataPath) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } +} + +func restoreModels(basePath string) { + + files, err := ioutil.ReadDir(basePath) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + // Migrate models + for _, mod := range gou.Models { + fmt.Printf("\r%s", strings.Repeat(" ", 80)) + fmt.Printf(color.GreenString(L("\rUpdate schema model: %s (%s) "), mod.Name, mod.MetaData.Table.Name)) + err := mod.Migrate(true) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + } + + fmt.Println("") + + for _, file := range files { + namer := strings.Split(file.Name(), ".") + name := strings.Join(namer[:len(namer)-2], ".") + if mod, has := gou.Models[name]; has { + fmt.Printf("\r%s", strings.Repeat(" ", 80)) + fmt.Printf(color.GreenString(L("\rRestore model: %s (%s) "), mod.Name, mod.MetaData.Table.Name)) + err := mod.Import(filepath.Join(basePath, file.Name())) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + } + } +} + +func unzipFile(file string, process func(file string)) string { + _, err := os.Stat(file) + + if errors.Is(err, os.ErrNotExist) { + fmt.Println(color.RedString("%s not exists", file)) + os.Exit(1) + } + + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + dst := filepath.Join(os.TempDir(), fmt.Sprintf("%s-%s", filepath.Base(file), time.Now().Format("20060102150405"))) + os.MkdirAll(dst, 0755) + + archive, err := zip.OpenReader(file) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + defer archive.Close() + + for _, f := range archive.File { + filePath := filepath.Join(dst, f.Name) + process(f.Name) + + if !strings.HasPrefix(filePath, filepath.Clean(dst)+string(os.PathSeparator)) { + fmt.Println(color.RedString(L("Fatal: invalid file path"))) + os.Exit(1) + } + if f.FileInfo().IsDir() { + os.MkdirAll(filePath, os.ModePerm) + continue + } + + if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + fileInArchive, err := f.Open() + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + if _, err := io.Copy(dstFile, fileInArchive); err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + dstFile.Close() + fileInArchive.Close() + } + + return dst +} diff --git a/cmd/root.go b/cmd/root.go index fc40b6f0..eb095106 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -45,6 +45,7 @@ var langs = map[string]string{ "NEXT:": "下一步:", "Listening": " 监听", "✨LISTENING✨": "✨服务正在运行✨", + "✨STOPPED✨": "✨服务已停止✨", "SessionPort": "会话服务端口", "Force migrate": "强制更新数据表结构", "Migrate is not allowed on production mode.": "Migrate 不能再生产环境下使用", @@ -91,6 +92,11 @@ func init() { startCmd, runCmd, initCmd, + serviceCmd, + dumpCmd, + restoreCmd, + socketCmd, + websocketCmd, ) // rootCmd.SetHelpCommand(helpCmd) rootCmd.PersistentFlags().StringVarP(&appPath, "app", "a", "", L("Application directory")) @@ -105,42 +111,25 @@ func Execute() { } } -// Boot 设定配置 (这个逻辑有 BUG ) +// Boot 设定配置 func Boot() { - - if envFile == "" && appPath != "" { - root, err := filepath.Abs(appPath) + root := config.Conf.Root + if appPath != "" { + r, err := filepath.Abs(appPath) if err != nil { exception.New("Root error %s", 500, err.Error()).Throw() } + root = r + } + if envFile != "" { + config.Conf = config.LoadFrom(envFile) + } else { config.Conf = config.LoadFrom(filepath.Join(root, ".env")) - config.Conf.Root = root - if config.Conf.Mode == "production" { - config.Production() - } else if config.Conf.Mode == "development" { - config.Development() - } - - return } - if envFile != "" && appPath == "" { // 指定环境变量文件 - // config.SetEnvFile(envFile) - config.Conf = config.LoadFrom(envFile) - } - - if envFile != "" && appPath != "" { - root, err := filepath.Abs(appPath) - if err != nil { - exception.New("Root error %s", 500, err.Error()).Throw() - } - config.Conf = config.LoadFrom(envFile) - config.Conf.Root = root - if config.Conf.Mode == "production" { - config.Production() - } else if config.Conf.Mode == "development" { - config.Development() - } - + if config.Conf.Mode == "production" { + config.Production() + } else if config.Conf.Mode == "development" { + config.Development() } } diff --git a/cmd/run.go b/cmd/run.go index 28e0a141..7e9b88e4 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -20,6 +20,7 @@ var runCmd = &cobra.Command{ Short: L("Execute process"), Long: L("Execute process"), Run: func(cmd *cobra.Command, args []string) { + defer share.SessionStop() defer gou.KillPlugins() defer func() { err := exception.Catch(recover()) diff --git a/cmd/service.go b/cmd/service.go new file mode 100644 index 00000000..59e516fd --- /dev/null +++ b/cmd/service.go @@ -0,0 +1,173 @@ +package cmd + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/fatih/color" + "github.com/spf13/cobra" + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/exception" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/share" +) + +var serviceCmd = &cobra.Command{ + Use: "service", + Short: L("Service manager"), + Long: L("Service manager"), + Run: func(cmd *cobra.Command, args []string) { + loadService(config.Conf) + command := "ps" + if len(args) > 0 { + command = args[0] + } + + if len(args) > 1 { + args = args[1:] + } else { + args = []string{} + } + + switch command { + case "ps": + services() + break + case "install", "start", "stop", "remove", "status": + serviceManage(command, args) + break + default: + serviceHelp() + } + }, +} + +func serviceHelp() { + fmt.Println("Usage:") + fmt.Println(" ", color.GreenString(L("%s service [ps|start|stop|install|status] [all|name]"), share.BUILDNAME)) + fmt.Println("") +} + +func serviceManageAll(command string) { + for name := range gou.Services { + if name == "all" { + continue + } + serviceManage(command, []string{name}) + } +} + +func serviceManage(command string, args []string) { + if len(args) == 0 { + serviceHelp() + os.Exit(1) + } + + defer func() { + err := exception.Catch(recover()) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + } + }() + + name := args[0] + if name == "all" { + serviceManageAll(command) + return + } + + srv := gou.SelectService(name) + var status = "" + var err error + switch command { + case "install": + status, err = srv.Install() + break + case "start": + status, err = srv.Start() + break + case "stop": + status, err = srv.Stop() + break + case "remove": + status, err = srv.Remove() + break + case "status": + status, err = srv.Status() + break + } + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + } + + fmt.Println(status) +} + +func services() { + for name, srv := range gou.Services { + status, err := srv.Status() + if err != nil { + status = err.Error() + } + fmt.Println( + color.GreenString("%s", name), + srv.Name, "....", + statusInfo(status), + ) + } + fmt.Println("") +} + +func statusInfo(message string) string { + if strings.Contains(message, "stopped") { + return statusStop() + } else if strings.Contains(message, "not installed") { + return statusNotInstalled() + } else if strings.Contains(message, "running") { + return statusRunning() + } + return statusUnknown() +} + +func statusUnknown() string { + return fmt.Sprintf("[ %s ]", color.WhiteString("UNKNOWN")) +} + +func statusRunning() string { + return fmt.Sprintf("[ %s ]", color.GreenString("RUNNING")) +} + +func statusNotInstalled() string { + return fmt.Sprintf("[ %s ]", color.YellowString("NOT INSTALLED")) +} + +func statusStop() string { + return fmt.Sprintf("[ %s ]", color.RedString("STOPED")) +} + +// Load 加载API +func loadService(cfg config.Config) { + var root = filepath.Join(cfg.Root, "services") + loadServiceFrom(root, "") +} + +func loadServiceFrom(dir string, prefix string) error { + + if share.DirNotExists(dir) { + return fmt.Errorf("%s does not exists", dir) + } + + err := share.Walk(dir, ".srv.json", func(root, filename string) { + name := prefix + share.SpecName(root, filename) + content := share.ReadFile(filename) + _, err := gou.LoadService(string(content), name) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + }) + + return err +} diff --git a/cmd/socket.go b/cmd/socket.go new file mode 100644 index 00000000..fd5a1948 --- /dev/null +++ b/cmd/socket.go @@ -0,0 +1,110 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/fatih/color" + jsoniter "github.com/json-iterator/go" + "github.com/spf13/cobra" + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/exception" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/engine" + "github.com/yaoapp/yao/share" +) + +var socketCmd = &cobra.Command{ + Use: "socket", + Short: L("Open a socket connection"), + Long: L("Open a socket connection"), + Run: func(cmd *cobra.Command, args []string) { + defer share.SessionStop() + defer gou.KillPlugins() + defer func() { + err := exception.Catch(recover()) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + } + }() + + Boot() + cfg := config.Conf + cfg.Session.IsCLI = true + engine.Load(cfg) + if len(args) < 1 { + fmt.Println(color.RedString(L("Not enough arguments"))) + fmt.Println(color.WhiteString(share.BUILDNAME + " help")) + return + } + + name := args[0] + pargs := []interface{}{} + for i, arg := range args { + if i == 0 { + continue + } + + // 解析参数 + if strings.HasPrefix(arg, "::") { + arg := strings.TrimPrefix(arg, "::") + var v interface{} + err := jsoniter.Unmarshal([]byte(arg), &v) + if err != nil { + fmt.Println(color.RedString(L("Arguments: %s"), err.Error())) + return + } + pargs = append(pargs, v) + fmt.Println(color.WhiteString("args[%d]: %s", i-1, arg)) + } else if strings.HasPrefix(arg, "\\::") { + arg := "::" + strings.TrimPrefix(arg, "\\::") + pargs = append(pargs, arg) + fmt.Println(color.WhiteString("args[%d]: %s", i-1, arg)) + } else { + pargs = append(pargs, arg) + fmt.Println(color.WhiteString("args[%d]: %s", i-1, arg)) + } + + } + + socket, has := gou.Sockets[name] + if !has { + fmt.Println(color.RedString(L("%s not exists!"), name)) + return + } + + if socket.Mode != "client" { + fmt.Println(color.RedString(L("%s not supported yet!"), socket.Mode)) + return + } + + host := socket.Host + port := socket.Port + argsLen := len(pargs) + if argsLen > 0 { + if inputHost, ok := pargs[0].(string); ok { + host = inputHost + } + } + + if argsLen > 1 { + if inputPort, ok := pargs[1].(string); ok { + port = inputPort + } + } + + fmt.Println(color.WhiteString("\n---------------------------------")) + fmt.Println(color.WhiteString(socket.Name)) + fmt.Println(color.WhiteString("---------------------------------")) + fmt.Println(color.GreenString("Mode: %s", socket.Mode)) + fmt.Println(color.GreenString("Host: %s://%s", socket.Protocol, host)) + fmt.Println(color.GreenString("Port: %s", port)) + fmt.Println(color.WhiteString("--------------------------------------")) + err := socket.Open(pargs...) + if err != nil { + fmt.Println(color.RedString(L("%s"), err.Error())) + return + } + + }, +} diff --git a/cmd/start.go b/cmd/start.go index a6667c66..a712cd72 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -1,14 +1,21 @@ package cmd import ( + "context" "fmt" "os" + "os/signal" "path/filepath" "strings" + "syscall" + "time" "github.com/fatih/color" "github.com/spf13/cobra" "github.com/yaoapp/gou" + "github.com/yaoapp/gou/task" + "github.com/yaoapp/gou/websocket" + "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/engine" "github.com/yaoapp/yao/service" @@ -16,14 +23,19 @@ import ( ) var startDebug = false -var startAlpha = false +var startDisableWatching = false var startCmd = &cobra.Command{ Use: "start", Short: L("Start Engine"), Long: L("Start Engine"), Run: func(cmd *cobra.Command, args []string) { - defer service.Stop(func() { fmt.Println(L("Service stopped")) }) + + // recive interrupt signal + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) + + // defer service.Stop(func() { fmt.Println(L("Service stopped")) }) Boot() if startDebug { // 强制 debug 模式启动 @@ -46,27 +58,6 @@ var startCmd = &cobra.Command{ host = "127.0.0.1" } - if mode == "development" { - fmt.Println(color.WhiteString("\n---------------------------------")) - fmt.Println(color.WhiteString(L("API List"))) - fmt.Println(color.WhiteString("---------------------------------")) - - for _, api := range gou.APIs { // API信息 - if len(api.HTTP.Paths) <= 0 { - continue - } - - fmt.Printf(color.CyanString("\n%s(%d)\n", api.Name, len(api.HTTP.Paths))) - for _, p := range api.HTTP.Paths { - fmt.Println( - colorMehtod(p.Method), - color.WhiteString(filepath.Join("/api", api.HTTP.Group, p.Path)), - "\tprocess:", p.Process) - } - - } - } - fmt.Println(color.WhiteString("\n---------------------------------")) fmt.Println(color.WhiteString(share.App.Name), color.WhiteString(share.App.Version), mode) fmt.Println(color.WhiteString("---------------------------------")) @@ -78,33 +69,135 @@ var startCmd = &cobra.Command{ fmt.Println(color.WhiteString(L("Frontend")), color.GreenString(" http://%s%s/", host, port)) fmt.Println(color.WhiteString(L("Dashboard")), color.GreenString(" http://%s%s/xiang/login/admin", host, port)) fmt.Println(color.WhiteString(L("API")), color.GreenString(" http://%s%s/api", host, port)) - fmt.Println(color.WhiteString(L("SessionPort")), color.GreenString(" %d", share.SessionPort)) fmt.Println(color.WhiteString(L("Listening")), color.GreenString(" %s:%d", config.Conf.Host, config.Conf.Port)) - fmt.Println("") - - // 调试模式 + // development mode if mode == "development" { + printApis(false) + printTasks(false) + printSchedules(false) + } + + if mode == "development" && !startDisableWatching { + // Watching + fmt.Println(color.WhiteString("\n---------------------------------")) + fmt.Println(color.WhiteString(L("Watching"))) + fmt.Println(color.WhiteString("---------------------------------")) service.Watch(config.Conf) } - // 启用内测功能 - if startAlpha { - for _, srv := range gou.Servers { - fmt.Println(color.WhiteString("\n---------------------------------")) - fmt.Println(color.WhiteString(srv.Name)) - fmt.Println(color.WhiteString("---------------------------------")) - fmt.Println(color.GreenString("Host: %s://%s", srv.Protocol, srv.Host)) - fmt.Println(color.GreenString("Port: %s\n\n", srv.Port)) - go srv.Start() - } + if mode == "production" { + printApis(true) + printTasks(true) + printSchedules(true) } + // Start server + go service.Start() fmt.Println(color.GreenString(L("✨LISTENING✨"))) - service.Start() + + for { + select { + case <-interrupt: + ctx, canceled := context.WithTimeout(context.Background(), (5 * time.Second)) + defer canceled() + service.StopWithContext(ctx, func() { + fmt.Println(color.GreenString(L("✨STOPPED✨"))) + }) + return + } + } }, } +func printSchedules(silent bool) { + if silent { + for name, sch := range gou.Schedules { + process := fmt.Sprintf("Process: %s", sch.Process) + if sch.TaskName != "" { + process = fmt.Sprintf("Task: %s", sch.TaskName) + } + log.Info("[Schedule] %s %s %s %s", sch.Schedule, name, sch.Name, process) + } + return + } + + fmt.Println(color.WhiteString("\n---------------------------------")) + fmt.Println(color.WhiteString(L("Schedules List (%d)"), len(gou.Schedules))) + fmt.Println(color.WhiteString("---------------------------------")) + for name, sch := range gou.Schedules { + process := fmt.Sprintf("Process: %s", sch.Process) + if sch.TaskName != "" { + process = fmt.Sprintf("Task: %s", sch.TaskName) + } + fmt.Printf(color.CyanString("[Schedule] %s %s", sch.Schedule, name)) + fmt.Printf(color.WhiteString("\t%s\t%s\n", sch.Name, process)) + } +} + +func printTasks(silent bool) { + + if silent { + for _, t := range task.Tasks { + log.Info("[Task] %s workers:%d", t.Option.Name, t.Option.WorkerNums) + } + return + } + + fmt.Println(color.WhiteString("\n---------------------------------")) + fmt.Println(color.WhiteString(L("Tasks List (%d)"), len(task.Tasks))) + fmt.Println(color.WhiteString("---------------------------------")) + for _, t := range task.Tasks { + fmt.Printf(color.CyanString("[Task] %s", t.Option.Name)) + fmt.Printf(color.WhiteString("\t workers: %d\n", t.Option.WorkerNums)) + } +} + +func printApis(silent bool) { + + if silent { + for _, api := range gou.APIs { + if len(api.HTTP.Paths) <= 0 { + continue + } + log.Info("[API] %s(%d)", api.Name, len(api.HTTP.Paths)) + for _, p := range api.HTTP.Paths { + log.Info("%s %s %s", p.Method, filepath.Join("/api", api.HTTP.Group, p.Path), p.Process) + } + } + for name, upgrader := range websocket.Upgraders { // WebSocket + log.Info("[WebSocket] GET /websocket/%s process:%s", name, upgrader.Process) + } + return + } + + fmt.Println(color.WhiteString("\n---------------------------------")) + fmt.Println(color.WhiteString(L("API List"))) + fmt.Println(color.WhiteString("---------------------------------")) + + for _, api := range gou.APIs { // API信息 + if len(api.HTTP.Paths) <= 0 { + continue + } + + fmt.Printf(color.CyanString("\n%s(%d)\n", api.Name, len(api.HTTP.Paths))) + for _, p := range api.HTTP.Paths { + fmt.Println( + colorMehtod(p.Method), + color.WhiteString(filepath.Join("/api", api.HTTP.Group, p.Path)), + "\tprocess:", p.Process) + } + } + + fmt.Printf(color.CyanString("\n%s(%d)\n", "WebSocket", len(websocket.Upgraders))) + for name, upgrader := range websocket.Upgraders { // WebSocket + fmt.Println( + colorMehtod("GET"), + color.WhiteString(filepath.Join("/websocket", name)), + "\tprocess:", upgrader.Process) + } +} + func colorMehtod(method string) string { method = strings.ToUpper(method) switch method { @@ -119,5 +212,5 @@ func colorMehtod(method string) string { func init() { startCmd.PersistentFlags().BoolVarP(&startDebug, "debug", "", false, L("Development mode")) - startCmd.PersistentFlags().BoolVarP(&startAlpha, "alpha", "", false, L("Enabled unstable features")) + startCmd.PersistentFlags().BoolVarP(&startDisableWatching, "disable-watching", "", false, L("Disable watching")) } diff --git a/cmd/version.go b/cmd/version.go index 929bb829..5c7b4b26 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -12,7 +12,13 @@ var versionCmd = &cobra.Command{ Short: L("Show version"), Long: L("Show version"), Run: func(cmd *cobra.Command, args []string) { + + version := share.VERSION + if share.PRVERSION != "" { + version = fmt.Sprintf("%s-%s", share.VERSION, share.PRVERSION) + } + // Do Stuff Here - fmt.Println(share.VERSION) + fmt.Println(version) }, } diff --git a/cmd/websocket.go b/cmd/websocket.go new file mode 100644 index 00000000..69cc66d8 --- /dev/null +++ b/cmd/websocket.go @@ -0,0 +1,71 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/fatih/color" + "github.com/spf13/cobra" + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/exception" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/engine" + "github.com/yaoapp/yao/share" +) + +var websocketCmd = &cobra.Command{ + Use: "websocket", + Short: L("Open a websocket connection"), + Long: L("Open a websocket connection"), + Run: func(cmd *cobra.Command, args []string) { + defer share.SessionStop() + defer gou.KillPlugins() + defer func() { + err := exception.Catch(recover()) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + } + }() + + Boot() + cfg := config.Conf + cfg.Session.IsCLI = true + engine.Load(cfg) + if len(args) < 1 { + fmt.Println(color.RedString(L("Not enough arguments"))) + fmt.Println(color.WhiteString(share.BUILDNAME + " help")) + return + } + + name := args[0] + websocket, has := gou.WebSockets[name] + if !has { + fmt.Println(color.RedString(L("%s not exists!"), name)) + return + } + + url := websocket.URL + protocols := websocket.Protocols + argsLen := len(args) + if argsLen > 1 { + url = args[1] + } + + if argsLen > 2 { + protocols = args[2:] + } + + fmt.Println(color.WhiteString("\n---------------------------------")) + fmt.Println(color.WhiteString(websocket.Name)) + fmt.Println(color.WhiteString("---------------------------------")) + fmt.Println(color.GreenString(" URL: %s", url)) + fmt.Println(color.GreenString("Protocols: %s", strings.Join(protocols, ","))) + fmt.Println(color.WhiteString("--------------------------------------")) + pargs := append([]string{url}, protocols...) + err := websocket.Open(pargs...) + if err != nil { + fmt.Println(color.RedString(L("%s"), err.Error())) + return + } + }, +} diff --git a/config/config.go b/config/config.go index 5857b9e8..99281cf6 100644 --- a/config/config.go +++ b/config/config.go @@ -60,7 +60,7 @@ func Load() Config { // Production 设定为生产环境 func Production() { Conf.Mode = "production" - log.SetLevel(log.ErrorLevel) + log.SetLevel(log.InfoLevel) log.SetFormatter(log.TEXT) if Conf.LogMode == "JSON" { log.SetFormatter(log.JSON) @@ -124,252 +124,3 @@ func CloseLog() { } } } - -// // Config 系统配置 -// type Config struct { -// XiangConfig -// Service ServiceConfig `json:"service,omitempty"` -// Session SessionConfig `json:"session,omitempty"` -// Database DatabaseConfig `json:"database,omitempty"` -// JWT JWTConfig `json:"jwt,omitempty"` -// Log LogConfig `json:"log,omitempty"` -// } - -// // JWTConfig JWT配置 -// type JWTConfig struct { -// Secret string `json:"secret,omitempty" env:"YAO_JWT_SECRET"` // JWT 密钥 -// } - -// // LogConfig 日志配置 -// type LogConfig struct { -// Access string `json:"access,omitempty" env:"XIANG_LOG_ACCESS" envDefault:"os://stdout"` // 服务访问日志 -// Error string `json:"error,omitempty" env:"XIANG_LOG_ERROR" envDefault:"os://stderr"` // 服务错误日志 -// DB string `json:"database,omitempty" env:"XIANG_LOG_DB" envDefault:"os://stdout"` // 数据库日志 -// Plugin string `json:"plugin,omitempty" env:"XIANG_LOG_PLUGIN" envDefault:"os://stdout"` // 插件日志 -// } - -// // XiangConfig 象传应用引擎配置 -// type XiangConfig struct { -// Mode string `json:"mode,omitempty" env:"XIANG_MODE" envDefault:"release"` // 象传引擎模式 debug/release/test -// Source string `json:"source,omitempty" env:"XIANG_SOURCE" envDefault:"fs://."` // 源码路径(用于单元测试载入数据) -// Path string `json:"path,omitempty" env:"XIANG_PATH" envDefault:"bin://xiang"` // 引擎文件目录 -// Root string `json:"root,omitempty" env:"XIANG_ROOT" envDefault:"fs://."` // 应用文件目录 -// RootUI string `json:"root_ui,omitempty" env:"XIANG_ROOT_UI"` // 应用界面静态文件目录 -// RootDB string `json:"root_db,omitempty" env:"XIANG_ROOT_DB"` // 应用SQLite数据库目录 -// RootData string `json:"root_data,omitempty" env:"XIANG_ROOT_DATA"` // 应用数据文件目录 -// RootAPI string `json:"root_api,omitempty" env:"XIANG_ROOT_API"` // 应用API文件目录 -// RootModel string `json:"root_model,omitempty" env:"XIANG_ROOT_MODEL"` // 应用模型文件目录 -// RootFLow string `json:"root_flow,omitempty" env:"XIANG_ROOT_FLOW"` // 应用业务逻辑文件目录 -// RootPlugin string `json:"root_plugin,omitempty" env:"XIANG_ROOT_PLUGIN"` // 应用业务插件文件目录 -// RootLib string `json:"root_lib,omitempty" env:"XIANG_ROOT_LIB"` // 应用资料库文件目录 -// RootTable string `json:"root_table,omitempty" env:"XIANG_ROOT_TABLE"` // 应用数据表格文件目录 -// RootChart string `json:"root_chart,omitempty" env:"XIANG_ROOT_CHART"` // 应用分析图表文件目录 -// RootPage string `json:"root_page,omitempty" env:"XIANG_ROOT_PAGE"` // 应用通用页面文件目录 -// RootScreen string `json:"root_screen,omitempty" env:"XIANG_ROOT_SCREEN"` // 应用大屏文件目录 -// RootForm string `json:"root_form,omitempty" env:"XIANG_ROOT_FORM"` // 表单文件目录 -// RootWorkFlow string `json:"root_workflow,omitempty" env:"XIANG_ROOT_WORKFLOW"` // 工作流文件目录 -// } - -// // ServiceConfig 服务配置 -// type ServiceConfig struct { -// Debug bool `json:"debug,omitempty" env:"XIANG_SERVICE_DEBUG" envDefault:"false"` // DEBUG 开关 -// HTTPS bool `json:"https,omitempty" env:"XIANG_SERVICE_HTTPS" envDefault:"false"` // HTTPS 开关 -// Cert string `json:"cert,omitempty" env:"XIANG_SERVICE_CERT"` // HTTPS 证书 -// Key string `json:"key,omitempty" env:"XIANG_SERVICE_KEY"` // HTTPS 证书密钥 -// Allow []string `json:"allow,omitempty" env:"XIANG_SERVICE_ALLOW" envSeparator:"|"` // 跨域访问域名列表 -// Host string `json:"host,omitempty" env:"XIANG_SERVICE_HOST" envDefault:"127.0.0.1"` // 服务监听IP -// Port int `json:"port,omitempty" env:"XIANG_SERVICE_PORT" envDefault:"5099"` // 服务监听端口 -// } - -// SessionConfig 会话服务器 -type SessionConfig struct { - Debug bool `json:"debug,omitempty" env:"XIANG_SESSION_DEBUG" envDefault:"false"` // DEBUG 开关 - Hosting bool `json:"hosting,omitempty" env:"XIANG_SESSION_HOSTING" envDefault:"true"` // 会话服务器 - IsCLI bool `json:"iscli,omitempty" env:"XIANG_SESSION_ISCLI" envDefault:"false"` // 是否为客户端启动 - Host string `json:"host,omitempty" env:"XIANG_SESSION_HOST" envDefault:"127.0.0.1"` // 会话服务器IP - Port int `json:"port,omitempty" env:"XIANG_SESSION_PORT" envDefault:"3322"` // 会话服务器端口 -} - -// // DatabaseConfig 数据库配置 -// type DatabaseConfig struct { -// Debug bool `json:"debug,omitempty" env:"XIANG_DB_DEBUG" envDefault:"false"` // DEBUG 开关 -// Driver string `json:"driver,omitempty" env:"XIANG_DB_DRIVER" envDefault:"sqlite3"` // 数据库驱动 ( sqlite3, mysql, postgres) -// Primary []string `json:"primary,omitempty" env:"XIANG_DB_PRIMARY" envSeparator:"|" envDefault:"file:./db/xiang.db"` // 主库连接DSN -// Secondary []string `json:"secondary,omitempty" env:"XIANG_DB_SECONDARY" envSeparator:"|"` // 从库连接DSN -// AESKey string `json:"aeskey,omitempty" env:"XIANG_DB_AESKEY"` // 加密存储KEY -// } - -// // NewConfig 创建配置文件 -// func NewConfig(envfile ...string) Config { - -// filename := os.Getenv("XIANG_ENV_FILE") -// if filename == "" { -// filename = ".env" -// } - -// if len(envfile) > 0 { -// file, err := filepath.Abs(envfile[0]) -// if err == nil { -// // log.Printf("加载环境配置文件%s出错 %s\n", envfile[0], err.Error()) -// // } else { -// filename = file -// } -// } - -// godotenv.Overload(filename) -// // if err != nil { -// // log.Printf("加载环境配置文件%s出错 %s\n", filename, err.Error()) -// // } - -// cfg := Config{} -// if err := env.Parse(&cfg); err != nil { -// exception.New("解析配置文件出错 %s", 500, err.Error()).Throw() -// } - -// cfg.SetDefaults() -// return cfg -// } - -// // NewConfigFrom 创建配置文件 -// func NewConfigFrom(input io.Reader) Config { -// cfg := Config{} -// err := helper.UnmarshalFile(input, &cfg) -// if err != nil { -// exception.New("解析配置文件出错 %s", 500, err.Error()).Throw() -// } -// cfg.SetDefaults() -// return cfg -// } - -// // SetDefaults 设定默认值 -// func (cfg *Config) SetDefaults() { - -// pwd, err := os.Getwd() -// if err != nil { -// log.Fatal(err.Error()) -// } - -// if cfg.Root == "fs://." || cfg.Root == "." { -// cfg.Root = pwd -// } - -// if cfg.RootAPI == "" { -// cfg.RootAPI = cfg.Root + "/apis" -// } -// if cfg.RootFLow == "" { -// cfg.RootFLow = cfg.Root + "/flows" -// } -// if cfg.RootModel == "" { -// cfg.RootModel = cfg.Root + "/models" -// } -// if cfg.RootPlugin == "" { -// cfg.RootPlugin = cfg.Root + "/plugins" -// } - -// if cfg.RootTable == "" { -// cfg.RootTable = cfg.Root + "/tables" -// } - -// if cfg.RootLib == "" { -// cfg.RootLib = cfg.Root + "/libs" -// } - -// if cfg.RootChart == "" { -// cfg.RootChart = cfg.Root + "/charts" -// } - -// if cfg.RootPage == "" { -// cfg.RootPage = cfg.Root + "/pages" -// } - -// if cfg.RootWorkFlow == "" { -// cfg.RootWorkFlow = cfg.Root + "/workflows" -// } - -// if cfg.RootData == "" { -// cfg.RootData = cfg.Root + "/data" -// } - -// if cfg.RootDB == "" { -// cfg.RootDB = cfg.Root + "/db" -// } - -// if cfg.RootUI == "" { -// cfg.RootUI = cfg.Root + "/ui" -// } - -// if len(cfg.Database.Primary) > 0 { -// if strings.HasPrefix(cfg.Database.Primary[0], "file:.") { -// cfg.Database.Primary[0] = strings.ReplaceAll(cfg.Database.Primary[0], "file:.", pwd) -// } else if strings.HasPrefix(cfg.Database.Primary[0], "file:/") { -// cfg.Database.Primary[0] = strings.ReplaceAll(cfg.Database.Primary[0], "file:.", cfg.RootDB) -// } -// } - -// // 过滤数据 -// cfg.RootDB = strings.TrimPrefix(cfg.RootDB, "fs://") -// cfg.RootDB = strings.TrimPrefix(cfg.RootDB, "file://") -// cfg.RootData = strings.TrimPrefix(cfg.RootData, "fs://") -// cfg.RootData = strings.TrimPrefix(cfg.RootData, "file://") -// cfg.RootUI = strings.TrimPrefix(cfg.RootUI, "fs://") -// cfg.RootUI = strings.TrimPrefix(cfg.RootUI, "file://") -// cfg.RootLib = strings.TrimPrefix(cfg.RootLib, "fs://") -// cfg.RootLib = strings.TrimPrefix(cfg.RootLib, "file://") - -// } - -// // SetEnvFile 指定ENV文件 -// func SetEnvFile(filename string) { -// Conf = NewConfig(filename) -// } - -// // SetAppPath 设定应用目录 -// func SetAppPath(root string, envfile ...string) { - -// fullpath, err := filepath.Abs(root) -// if err != nil { -// log.Panic("设定应用目录%s出错 %s\n", root, err.Error()) -// } - -// // 创建应用目录 -// pathinfo, err := os.Stat(fullpath) -// if os.IsNotExist(err) { -// err := os.MkdirAll(fullpath, os.ModePerm) -// if err != nil { -// log.Panic("创建目录失败(%s) %s", root, err) -// } -// } -// pathinfo, err = os.Stat(fullpath) -// if !pathinfo.IsDir() { -// log.Panic("检查应用目录失败(%s) ", err) -// } - -// if !pathinfo.IsDir() { -// log.Panic("应用目录不是文件夹(%s) ", root) -// } - -// // Set ENV -// if len(envfile) > 0 { -// Conf = NewConfig(envfile[0]) -// } - -// // 从加载配置文件 -// Conf.Root = fullpath -// Conf.RootAPI = filepath.Join(fullpath, "/apis") -// Conf.RootFLow = filepath.Join(fullpath, "/flows") -// Conf.RootModel = filepath.Join(fullpath, "/models") -// Conf.RootPlugin = filepath.Join(fullpath, "/plugins") -// Conf.RootTable = filepath.Join(fullpath, "/tables") -// Conf.RootChart = filepath.Join(fullpath, "/charts") -// Conf.RootPage = filepath.Join(fullpath, "/pages") -// Conf.RootWorkFlow = filepath.Join(fullpath, "/workflows") -// Conf.RootData = filepath.Join(fullpath, "/data") -// Conf.RootUI = filepath.Join(fullpath, "/ui") -// Conf.RootDB = filepath.Join(fullpath, "/db") -// Conf.RootLib = filepath.Join(fullpath, "/libs") -// } - -// // IsDebug 是否为调试模式 -// func IsDebug() bool { -// return Conf.Mode == "debug" -// } diff --git a/config/types.go b/config/types.go index 33d6f6f0..ebd3ed22 100644 --- a/config/types.go +++ b/config/types.go @@ -11,8 +11,9 @@ type Config struct { Log string `json:"log,omitempty" env:"YAO_LOG"` // 服务日志地址 LogMode string `json:"log_mode,omitempty" env:"YAO_LOG_MODE" envDefault:"TEXT"` // 服务日志模式 JSON|TEXT // Session string `json:"session,omitempty" env:"YAO_SESSION" envDefault:"memory"` // 用户会话模式 memory|redis|database - JWTSecret string `json:"jwt_secret,omitempty" env:"YAO_JWT_SECRET"` // JWT 密钥 - DB DBConfig `json:"db,omitempty"` // 数据库配置 + JWTSecret string `json:"jwt_secret,omitempty" env:"YAO_JWT_SECRET"` // JWT 密钥 + DB DBConfig `json:"db,omitempty"` // 数据库配置 + AllowFrom []string `json:"allowfrom,omitempty" envSeparator:"|" env:"YAO_ALLOW_FROM" ` // Domain list the separator is | Session SessionConfig `json:"session,omitempty"` } @@ -23,3 +24,15 @@ type DBConfig struct { Secondary []string `json:"secondary,omitempty" env:"YAO_DB_SECONDARY" envSeparator:"|"` // 从库连接DSN AESKey string `json:"aeskey,omitempty" env:"YAO_DB_AESKEY"` // 加密存储KEY } + +// SessionConfig 会话服务器 +type SessionConfig struct { + Store string `json:"store,omitempty" env:"YAO_SESSION_STORE" envDefault:"file"` // The session store. redis | file + File string `json:"file,omitempty" env:"YAO_SESSION_FILE"` // The file path + Host string `json:"host,omitempty" env:"YAO_SESSION_HOST" envDefault:"127.0.0.1"` // The redis host + Port string `json:"port,omitempty" env:"YAO_SESSION_PORT" envDefault:"6379"` // The redis port + Password string `json:"password,omitempty" env:"YAO_SESSION_PASSWORD"` // The redis password + Username string `json:"username,omitempty" env:"YAO_SESSION_USERNAME"` // The redis username + DB string `json:"db,omitempty" env:"YAO_SESSION_DB" envDefault:"1"` // The redis username + IsCLI bool `json:"iscli,omitempty" env:"YAO_SESSION_ISCLI" envDefault:"false"` // Command Line Start +} diff --git a/crypto/crypto.go b/crypto/crypto.go new file mode 100644 index 00000000..96f99954 --- /dev/null +++ b/crypto/crypto.go @@ -0,0 +1,61 @@ +package crypto + +import ( + "crypto" + "crypto/hmac" + "encoding/base64" + "fmt" + + "golang.org/x/crypto/md4" +) + +func init() { + crypto.RegisterHash(crypto.MD4, md4.New) +} + +var hashTypes = map[string]crypto.Hash{ + "MD4": crypto.MD4, + "MD5": crypto.MD5, + "SHA1": crypto.SHA1, + "SHA224": crypto.SHA224, + "SHA256": crypto.SHA256, + "SHA384": crypto.SHA384, + "SHA512": crypto.SHA512, + "MD5SHA1": crypto.MD5SHA1, + "RIPEMD160": crypto.RIPEMD160, + "SHA3_224": crypto.SHA3_224, + "SHA3_256": crypto.SHA3_256, + "SHA3_384": crypto.SHA3_384, + "SHA3_512": crypto.SHA3_512, + "SHA512_224": crypto.SHA512_224, + "SHA512_256": crypto.SHA512_256, + "BLAKE2s_256": crypto.BLAKE2s_256, + "BLAKE2b_256": crypto.BLAKE2b_256, + "BLAKE2b_384": crypto.BLAKE2b_384, + "BLAKE2b_512": crypto.BLAKE2b_512, +} + +// Hash string +func Hash(hash crypto.Hash, value string) (string, error) { + h := hash.New() + _, err := h.Write([]byte(value)) + if err != nil { + return "", err + } + return fmt.Sprintf("%x", h.Sum(nil)), nil +} + +// Hmac the Keyed-Hash Message Authentication Code (HMAC) +func Hmac(hash crypto.Hash, value string, key string, encoding ...string) (string, error) { + mac := hmac.New(hash.New, []byte(key)) + _, err := mac.Write([]byte(value)) + if err != nil { + return "", err + } + + if len(encoding) > 0 && encoding[0] == "base64" { + return base64.StdEncoding.EncodeToString(mac.Sum(nil)), nil + } + + return fmt.Sprintf("%x", mac.Sum(nil)), nil +} diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go new file mode 100644 index 00000000..2a85bbd4 --- /dev/null +++ b/crypto/crypto_test.go @@ -0,0 +1,68 @@ +package crypto + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou" +) + +func TestMD4(t *testing.T) { + // Hash + args := []interface{}{"MD4", "123456"} + res := gou.NewProcess("yao.crypto.hash", args...).Run() + assert.Equal(t, "585028aa0f794af812ee3be8804eb14a", res) + + // HMac + args = append(args, "123456") + res = gou.NewProcess("yao.crypto.hmac", args...).Run() + assert.Equal(t, "356f45727db95d65843b2794474d741c", res) +} + +func TestMD5(t *testing.T) { + // Hash + args := []interface{}{"MD5", "123456"} + res := gou.NewProcess("yao.crypto.hash", args...).Run() + assert.Equal(t, "e10adc3949ba59abbe56e057f20f883e", res) + + // HMac + args = append(args, "123456") + res = gou.NewProcess("yao.crypto.hmac", args...).Run() + assert.Equal(t, "30ce71a73bdd908c3955a90e8f7429ef", res) +} + +func TestSHA1(t *testing.T) { + // Hash + args := []interface{}{"SHA1", "123456"} + res := gou.NewProcess("yao.crypto.hash", args...).Run() + assert.Equal(t, "7c4a8d09ca3762af61e59520943dc26494f8941b", res) + + // HMac + args = append(args, "123456") + res = gou.NewProcess("yao.crypto.hmac", args...).Run() + assert.Equal(t, "74b55b6ab2b8e438ac810435e369e3047b3951d0", res) +} + +func TestSHA256(t *testing.T) { + // Hash + args := []interface{}{"SHA256", "123456"} + res := gou.NewProcess("yao.crypto.hash", args...).Run() + assert.Equal(t, "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", res) + + // HMac + args = append(args, "123456") + res = gou.NewProcess("yao.crypto.hmac", args...).Run() + assert.Equal(t, "b8ad08a3a547e35829b821b75370301dd8c4b06bdd7771f9b541a75914068718", res) +} + +func TestSHA1Base64(t *testing.T) { + // Hash + args := []interface{}{"SHA1", "123456"} + res := gou.NewProcess("yao.crypto.hash", args...).Run() + assert.Equal(t, "7c4a8d09ca3762af61e59520943dc26494f8941b", res) + + // HMac + args = append(args, "123456", "base64") + res = gou.NewProcess("yao.crypto.hmac", args...).Run() + assert.Equal(t, "dLVbarK45DisgQQ142njBHs5UdA=", res) +} diff --git a/crypto/process.go b/crypto/process.go new file mode 100644 index 00000000..235950cb --- /dev/null +++ b/crypto/process.go @@ -0,0 +1,59 @@ +package crypto + +import ( + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/exception" +) + +func init() { + gou.RegisterProcessHandler("yao.crypto.hash", ProcessHash) + gou.RegisterProcessHandler("yao.crypto.hmac", ProcessHmac) +} + +// ProcessHash yao.crypto.hash Crypto Hash +// Args[0] string: the hash function name. MD4/MD5/SHA1/SHA224/SHA256/SHA384/SHA512/MD5SHA1/RIPEMD160/SHA3_224/SHA3_256/SHA3_384/SHA3_512/SHA512_224/SHA512_256/BLAKE2s_256/BLAKE2b_256/BLAKE2b_384/BLAKE2b_512 +// Args[1] string: value +func ProcessHash(process *gou.Process) interface{} { + process.ValidateArgNums(2) + typ := process.ArgsString(0) + value := process.ArgsString(1) + + h, has := hashTypes[typ] + if !has { + exception.New("%s does not support", 400, typ).Throw() + } + + res, err := Hash(h, value) + if err != nil { + exception.New("%s error: %s value: %s", 400, typ, err, value).Throw() + } + return res +} + +// ProcessHmac yao.crypto.hmac Crypto the Keyed-Hash Message Authentication Code (HMAC) Hash +// Args[0] string: the hash function name. MD4/MD5/SHA1/SHA224/SHA256/SHA384/SHA512/MD5SHA1/RIPEMD160/SHA3_224/SHA3_256/SHA3_384/SHA3_512/SHA512_224/SHA512_256/BLAKE2s_256/BLAKE2b_256/BLAKE2b_384/BLAKE2b_512 +// Args[1] string: value +// Args[2] string: key +// Args[3] string: base64 +func ProcessHmac(process *gou.Process) interface{} { + process.ValidateArgNums(3) + typ := process.ArgsString(0) + value := process.ArgsString(1) + key := process.ArgsString(2) + + h, has := hashTypes[typ] + if !has { + exception.New("%s does not support", 400, typ).Throw() + } + + encoding := "" + if process.NumOfArgs() > 3 { + encoding = process.ArgsString(3) + } + + res, err := Hmac(h, value, key, encoding) + if err != nil { + exception.New("%s error: %s value: %s", 400, typ, err, value).Throw() + } + return res +} diff --git a/data/bindata.go b/data/bindata.go index e6284483..561e1c11 100644 --- a/data/bindata.go +++ b/data/bindata.go @@ -208,7 +208,7 @@ func uiIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "ui/index.html", size: 22, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "ui/index.html", size: 22, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -228,7 +228,7 @@ func yaoDs_store() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -248,7 +248,7 @@ func yaoApisReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/README.md", size: 13, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/apis/README.md", size: 13, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -268,7 +268,7 @@ func yaoApisChartHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/chart.http.json", size: 624, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/apis/chart.http.json", size: 624, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -288,7 +288,7 @@ func yaoApisImportHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/import.http.json", size: 1676, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/apis/import.http.json", size: 1676, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -308,12 +308,12 @@ func yaoApisPageHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/page.http.json", size: 609, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/apis/page.http.json", size: 609, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoApisStorageHttpJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x91\xcf\x4e\xfa\x40\x10\xc7\xef\x7d\x8a\xc9\xe6\x77\x2c\x6d\x7f\x1e\xb9\x79\x50\x62\x62\x22\x41\x38\x11\x0e\x63\x3b\x94\x62\xd9\x5d\xf7\x8f\x4a\x08\x17\x1f\x40\xaf\xde\x8d\x89\x17\x1f\x0b\x9f\xc3\xec\x52\x8a\x18\x4c\x08\xc1\x78\xd9\xa6\xf3\x99\xfd\xee\x7c\x76\x67\x01\x00\xe3\x38\x21\xd6\x04\xb6\x78\x7f\x5e\x3c\xbc\x7d\x3c\xbe\x2e\x9e\x5e\x58\xe8\xc8\x2d\x29\x5d\x08\xee\xe0\xff\x28\x89\x92\x65\x35\x23\x9d\xaa\x42\x9a\x8a\x7c\xdd\x76\xdc\x3e\x5b\xf6\xe4\x4a\x58\xe9\xe8\x7d\x81\x3c\x8f\xb5\x11\x0a\x73\xaa\x98\x45\x95\x39\x76\x45\xa8\x48\x35\xc6\x77\x66\x09\x24\x9a\x91\x66\x4d\xe8\x07\x00\x00\x33\xbf\x56\x65\xd7\x1e\x5b\x59\x0a\xcc\x7c\xaf\x07\x13\x32\x23\xe1\x93\xda\x17\x97\xdd\x75\xbd\x3e\xa0\xb1\xae\x49\x25\x52\xd2\xba\x1e\x29\x1a\xea\xa8\xf7\x2d\xaf\x70\x3e\x7d\xf6\x6f\x58\x94\x14\xb9\x85\x0d\x6a\x26\xac\x61\xcd\x7a\x26\x00\xa6\x0d\x1a\xeb\xf2\x8e\x92\x24\x5c\x97\xcd\x54\xfa\xcb\x44\x29\xcb\x22\x45\x77\x49\xf1\x58\x0b\xce\xaa\x96\xb9\xff\xce\xc3\x1f\x14\x15\x61\xe6\x4f\xde\x22\xd9\x3a\xd9\xc3\xb1\x43\x98\x9d\x6e\x04\xae\x2c\x6f\x2c\xa9\x69\xe4\x1f\x3f\x84\xd5\x2f\xf1\x54\x64\x7f\xe0\x6d\x55\x79\x30\xe5\x16\x99\x5e\xe7\x7c\x47\x61\x3f\xf7\xfe\xba\xc5\x04\x73\x8a\x25\xcf\x77\xf4\x34\xe2\x9a\xf8\x21\x4d\xbb\x9b\x81\x5b\x5c\x7f\xed\x2d\x03\x80\x41\x30\x0f\x3e\x03\x00\x00\xff\xff\xb6\x5b\xa0\x8d\x44\x04\x00\x00") +var _yaoApisStorageHttpJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x92\xbd\x4e\xfb\x30\x10\xc0\xf7\x3c\x85\x65\xfd\xc7\x34\xc9\x9f\xb1\x1b\x03\x54\x48\x48\x54\xa5\x9d\xaa\x0e\x47\x72\x4d\x5d\x52\xdb\xf8\x03\xa8\xaa\x2e\x3c\x00\xac\xec\x08\x89\x85\xc7\x2a\xcf\x81\xec\xa4\xe9\x07\x85\x22\x24\xc4\xe2\xc8\xf7\xbb\xf3\xf9\xe7\xcb\x2c\x20\x84\x72\x98\x20\x6d\x12\xba\x78\x7d\x5c\xdc\xbd\xbc\xdd\x3f\x2f\x1e\x9e\x68\xe8\xc8\x35\x2a\xcd\x04\x77\xf0\x7f\x94\x44\x49\x19\xcd\x50\xa7\x8a\x49\x53\x91\xf5\xb2\xc3\xf6\x49\x99\x93\x2b\x61\xa5\xa3\xb7\x0c\x78\x1e\x6b\x23\x14\xe4\x58\x31\x0b\x2a\x73\xec\x02\x41\xa1\x6a\x8c\x6f\x4c\x09\x24\x98\x91\xa6\x4d\xd2\x0f\x08\x21\x64\xe6\xd7\x2a\xec\xd2\x63\x2b\x0b\x01\x99\xcf\xf5\x60\x82\x66\x24\xfc\x49\xed\xb3\xf3\xee\x2a\x2e\x95\x48\x51\xeb\xba\x7d\x34\xd4\x51\x6f\xab\x96\xb9\xbb\xf7\xe9\xbf\x21\x2b\x30\x72\x0b\x1d\xd4\x4c\x58\x43\x9b\x75\x7f\x42\xa8\x36\x60\xac\x3b\xef\x20\x49\xc2\x55\xd8\x4c\xa5\x7f\x38\x90\xb2\x60\x29\xb8\x07\x89\xc7\x5a\x70\x5a\xa5\xcc\xfd\x77\x1e\x7e\xa2\xa3\x10\x32\xdf\x79\x87\x50\xeb\x68\x8f\x4f\x07\x21\x3b\xde\x28\x5e\x1a\x5d\x59\x54\xd3\xc8\x0f\x35\x24\xcb\x2d\xf2\x54\x64\x7f\xe0\x68\x55\xb1\x57\xaf\xfe\x1f\x1a\x5f\x2b\xb7\xd0\xf4\x3a\xa7\xdf\x14\xf6\xf7\x5e\xdb\x8a\x4b\xe4\x1f\xf5\xb7\xb4\x57\xba\x6c\x02\x39\xc6\x92\xe7\x74\x9f\x61\x79\xf2\x4f\x46\xd8\x42\xd3\xdd\x2c\xde\x61\xf4\x6b\x13\x0b\x08\x19\x04\xf3\xe0\x3d\x00\x00\xff\xff\xeb\x8c\x85\x1f\x02\x04\x00\x00") func yaoApisStorageHttpJsonBytes() ([]byte, error) { return bindataRead( @@ -328,12 +328,12 @@ func yaoApisStorageHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/storage.http.json", size: 1092, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/apis/storage.http.json", size: 1026, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoApisTableHttpJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd4\x56\xcd\x6e\xd3\x40\x10\xbe\xe7\x29\x56\xab\x1e\x53\x3b\x70\xf4\x0d\x89\x1f\xe5\x44\x51\x41\x1c\xaa\x1e\xa6\xde\x21\x99\xd6\x5e\x6f\xf7\x27\xc1\x54\x79\x04\xe0\xc4\x1d\x84\x90\x38\x21\x1e\x80\xd7\x21\xe2\x31\xd0\xae\x4d\x52\x87\xa4\x0d\xb1\x90\xd3\x4b\xa2\x9d\x99\x6f\x34\xdf\xf7\x79\xec\xbd\xea\x31\xc6\x25\xe4\xc8\x13\xc6\xe7\x1f\xbe\xcf\xdf\x7e\xfb\xf5\xe9\xeb\xfc\xe3\x8f\xf9\xbb\x2f\x3f\xdf\x7f\xe6\x7d\x9f\x9f\xa0\x36\x54\x48\x5f\x72\x2f\x1a\x44\x83\x2a\x2a\xd0\xa4\x9a\x94\xad\x33\x7f\x83\x1f\x1c\x0d\xab\xca\x91\x2e\x9c\xf2\x35\xaf\x09\xe4\x28\xb6\x70\x96\x61\x9d\x71\xa0\x85\xcf\x9c\x21\x68\xd4\x87\xe7\x53\x5b\x25\x14\xd8\xb1\xe1\x09\x3b\xe9\x31\xc6\xd8\x55\xf8\xad\xc3\xbe\x3c\x4e\xfc\xcc\xb1\x41\xd0\xe9\x38\x20\x42\x3a\x47\x3b\x2e\x42\xbf\x27\x8f\x9e\x2f\xc3\x4a\x17\x29\x1a\xb3\x98\x20\x0a\x13\x44\xc7\x2b\x68\xf2\x3c\x4e\xf8\x81\x02\x0d\x79\x14\x44\xe9\x33\x9e\x5c\x3a\xd4\xe5\x61\x08\xfa\xf3\x41\x38\x47\x0a\x46\xb8\x72\x34\xf4\x06\xf9\xe9\xa2\x5d\xe1\x2c\x4f\x16\xa3\x33\xc6\x8d\x05\xeb\xfc\x14\xf7\x07\x83\xfe\x32\x6c\x4b\x15\xd4\x07\xa5\x32\x4a\xc1\xeb\x19\x9f\x9b\x42\xf2\xba\x64\x16\xfe\x67\xfd\x1b\x95\x78\x45\x52\xc4\x09\x89\x5d\xb5\x78\x4c\x52\xdc\xa6\x44\x7d\x24\xd1\x15\x49\x83\x19\xa6\x76\x77\xbb\x9b\xe8\x8a\xe4\x72\xc4\x06\xdb\x6b\xe1\xca\xe0\x0b\x2c\xa7\x85\x16\x6b\x32\x1b\x00\x13\xc8\xdc\xba\x78\x46\x39\xd9\x3f\xb4\xbb\x12\x12\x26\xb8\x4e\xc6\xa3\xa7\xc7\x5b\xe8\xd8\x00\x6f\x58\x1a\x05\x65\x56\x40\x67\x4f\xca\xa5\xa3\xf4\xa2\x15\xcb\x67\xbe\xc3\x5d\xa0\x2a\x30\x43\x8b\x9b\x76\x7f\x2b\xae\x0f\x43\x8b\xbd\x5f\x7f\x92\x06\xf5\xda\xf5\xdf\x8a\xe5\x70\x05\xbe\x91\x65\xb0\x33\x4a\x8b\xcc\xe5\xd2\x34\x62\x61\xa7\x4d\xc7\x4e\x4f\xc7\xa8\x77\x7f\xae\x2b\xaf\x5f\x36\x7b\x6c\xf1\xe5\xeb\x96\x33\xc9\x96\x84\x87\xf2\x56\xe3\xab\xd7\x33\x09\x73\xfd\xb3\xae\x29\x07\x5d\x76\xc5\xde\x29\x01\xad\x1d\x7f\x11\x9a\xfc\xbb\xe3\x7b\xf0\x6e\xab\xf9\xb7\x70\xbf\x22\xdf\xd2\xfd\x3d\x50\xc2\xa0\xb5\x24\x47\xbb\xdf\x7d\x56\xe0\x37\xaa\x50\xdf\xb3\xfe\x1b\xd5\x1e\x63\xa7\xbd\x59\xef\x77\x00\x00\x00\xff\xff\x8a\x3e\xea\x17\x7c\x0c\x00\x00") +var _yaoApisTableHttpJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd4\x56\xcb\x6e\xd3\x40\x14\xdd\xfb\x2b\x46\xa3\x2e\x53\x3b\xb0\xf4\x0e\x89\x87\xb2\xa2\xa8\x20\x16\x55\x17\x17\xcf\x25\x19\x6a\xcf\x4c\xe7\x91\x62\xaa\x7c\x02\xb0\x62\x0f\x42\x48\xac\x10\x1f\xc0\xef\x10\xf1\x19\x68\xc6\x26\xa9\x4d\xd2\x9a\x58\xc8\xe9\x26\xd1\x3d\xf7\xe1\x7b\xce\xb1\xc7\xbe\x8c\x08\xa1\x02\x0a\xa4\x29\xa1\xcb\x0f\xdf\x97\x6f\xbf\xfd\xfa\xf4\x75\xf9\xf1\xc7\xf2\xdd\x97\x9f\xef\x3f\xd3\x91\xcf\xcf\x51\x1b\x2e\x85\x2f\xb9\x13\x8f\xe3\x71\x85\x32\x34\x99\xe6\xca\xd6\x99\xbf\x9b\xef\x1d\x4d\xaa\xca\xa9\x96\x4e\xf9\x9a\xd7\x1c\xc4\x34\xb1\xf0\x22\xc7\x3a\xe3\x40\x33\x9f\x09\xd8\x61\x15\x86\x8c\x02\x3b\x33\x34\x25\x27\x11\x21\x84\x5c\x86\xdf\x1a\xf6\xf5\x49\xea\x97\x4e\x0c\x82\xce\x66\xa1\x23\xa4\x0b\xb4\x33\x19\x06\x3e\x7a\xf0\x74\x0d\x2b\x2d\x33\x34\x66\xb5\x42\x1c\x2e\x17\x1f\xb7\xba\xb9\x27\x72\x42\x0f\x14\x68\x28\xe2\xa0\xca\x88\xd0\xf4\xdc\xa1\x2e\x0f\x03\xe8\xe3\x83\x10\xc7\x0a\xa6\xd8\x0a\x0d\x7f\x83\xf4\x74\x35\x4e\x3a\x4b\xd3\xd5\xea\x84\x50\x63\xc1\x3a\xbf\xc5\xdd\xf1\x78\xb4\x86\x6d\xa9\x82\xfc\xa0\x54\xce\x33\xf0\x82\x26\xaf\x8c\x14\xb4\x2e\x59\x84\xff\xc5\xe8\x5a\x25\x5e\x72\xc1\x92\x94\xb3\x5d\xb5\x78\xc8\x05\xbb\x49\x89\x3a\xf4\x17\x69\xc9\x32\x10\x69\x83\x39\x66\x76\x77\xfb\x9b\xdd\x15\xe9\xf5\x8a\x0d\xf6\x57\xe0\xca\xf0\x33\x2c\x2f\xa4\x66\x1b\x32\x5b\x1a\xe6\x90\xbb\x4d\x78\xce\x0b\x6e\xff\xd0\x1e\x4a\x48\x98\xe3\x26\x19\x8f\x1e\x1f\x77\xd0\xb1\xd1\xbc\xe5\x21\x52\x50\xe6\x12\xd8\x50\x77\xca\xb9\xe3\xd9\x59\x2f\x96\x4f\xfc\x84\xdb\x40\x95\x61\x8e\x16\xb7\x9d\x05\x9d\xb8\xde\x0f\x23\xba\x1f\x07\x03\x31\xe5\xc2\xa0\xde\xf8\xf8\x77\x62\x39\x69\xb5\x6f\x65\x19\xec\x8c\x33\x99\xbb\x42\x98\x06\x16\x9e\x69\x33\xb0\xd3\x17\x33\xd4\xbb\xdf\xd7\x95\xd7\xcf\x9b\x33\x3a\xbc\x09\x87\xe5\xcc\x45\x4f\xc2\x13\x71\xa3\xf1\xd5\xf1\xcc\x99\xb9\xfa\x9a\xd7\xbc\x00\x5d\x0e\xc5\xde\x29\x06\xbd\x1d\x7f\x16\x86\xfc\xbb\xe3\x7b\x70\xb6\xd5\xfc\x7b\xb8\x5f\x91\xef\xe9\xfe\x1e\x28\x61\xd0\x5a\x2e\xa6\xbb\x7f\xfb\xb4\xda\xaf\x55\xa1\xfe\xce\xfa\x6f\x54\x23\x42\x4e\xa3\x45\xf4\x3b\x00\x00\xff\xff\x8d\x38\xb9\xa1\x8d\x0c\x00\x00") func yaoApisTableHttpJsonBytes() ([]byte, error) { return bindataRead( @@ -348,7 +348,7 @@ func yaoApisTableHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/table.http.json", size: 3196, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/apis/table.http.json", size: 3213, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -368,7 +368,7 @@ func yaoApisUserHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/user.http.json", size: 1453, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/apis/user.http.json", size: 1453, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -388,7 +388,7 @@ func yaoApisXiangHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/xiang.http.json", size: 1239, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/apis/xiang.http.json", size: 1239, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -408,7 +408,7 @@ func yaoDataDs_store() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/data/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -428,7 +428,7 @@ func yaoDataAppJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/app.json", size: 277, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/data/app.json", size: 277, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -448,7 +448,7 @@ func yaoDataIconsDs_store() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/data/icons/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -468,7 +468,7 @@ func yaoDataIcons404Png() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -488,7 +488,7 @@ func yaoDataIconsIconIcns() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -508,7 +508,7 @@ func yaoDataIconsIconIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -528,7 +528,7 @@ func yaoDataIconsIconPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -548,7 +548,7 @@ func yaoDataIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -568,7 +568,7 @@ func yaoFlowsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/README.md", size: 12, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/flows/README.md", size: 12, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -588,7 +588,7 @@ func yaoFlowsAdminGuardFlowJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/admin/guard.flow.json", size: 559, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/flows/admin/guard.flow.json", size: 559, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -608,7 +608,7 @@ func yaoFlowsMenuFlowJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/menu.flow.json", size: 1045, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/flows/menu.flow.json", size: 1045, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -628,7 +628,7 @@ func yaoFlowsUserFindFlowJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/user/find.flow.json", size: 328, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/flows/user/find.flow.json", size: 328, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -648,7 +648,7 @@ func yaoLangsEnUsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -668,7 +668,7 @@ func yaoModelsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/models/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -688,7 +688,7 @@ func yaoModelsMenuModJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/menu.mod.json", size: 2378, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/models/menu.mod.json", size: 2378, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -708,7 +708,7 @@ func yaoModelsUserModJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user.mod.json", size: 8826, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/models/user.mod.json", size: 8826, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -728,7 +728,7 @@ func yaoModelsWorkflowModJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/workflow.mod.json", size: 3333, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/models/workflow.mod.json", size: 3333, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -748,7 +748,7 @@ func yaoTablesReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/tables/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/tables/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -768,7 +768,7 @@ func yaoTablesMenuTabJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/tables/menu.tab.json", size: 10471, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/tables/menu.tab.json", size: 10471, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -788,7 +788,7 @@ func yaoTablesUserTabJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/tables/user.tab.json", size: 2357, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/tables/user.tab.json", size: 2357, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/docker/development/Dockerfile b/docker/development/Dockerfile index 71a61313..37498434 100644 --- a/docker/development/Dockerfile +++ b/docker/development/Dockerfile @@ -20,7 +20,7 @@ ARG ARCH RUN groupadd -r yao && useradd -r -g yao yao && \ apt-get update && \ apt-get install -y curl sudo procps net-tools -RUN curl -fsSL "https://release-sv.yaoapps.com/archives/yao-${VERSION}-linux-${ARCH}" > /usr/local/bin/yao && \ +RUN curl -fsSL "https://release-sv-1252011659.cos.na-siliconvalley.myqcloud.com/archives/yao-${VERSION}-linux-${ARCH}" > /usr/local/bin/yao && \ chmod +x /usr/local/bin/yao && \ mkdir -p /data/app diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile index 0af6d2c0..bb9def6b 100644 --- a/docker/production/Dockerfile +++ b/docker/production/Dockerfile @@ -18,7 +18,7 @@ FROM alpine:latest ARG VERSION ARG ARCH RUN apk --no-cache add curl -RUN curl -fsSL "https://release-sv.yaoapps.com/archives/yao-${VERSION}-linux-${ARCH}" > /usr/local/bin/yao && \ +RUN curl -fsSL "https://release-sv-1252011659.cos.na-siliconvalley.myqcloud.com/archives/yao-${VERSION}-linux-${ARCH}" > /usr/local/bin/yao && \ chmod +x /usr/local/bin/yao RUN mkdir -p /data/app && \ cd /data/app && /usr/local/bin/yao init && \ diff --git a/docs/architecture.png b/docs/architecture.png new file mode 100644 index 00000000..841fd5ca Binary files /dev/null and b/docs/architecture.png differ diff --git a/docs/wechat.jpg b/docs/wechat.jpg new file mode 100644 index 00000000..84ad1bd8 Binary files /dev/null and b/docs/wechat.jpg differ diff --git a/engine/load.go b/engine/load.go index 4046165a..87c5aa29 100644 --- a/engine/load.go +++ b/engine/load.go @@ -4,12 +4,14 @@ import ( "fmt" "os" "path/filepath" + "strings" + "github.com/fatih/color" "github.com/yaoapp/gou" "github.com/yaoapp/kun/exception" - "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/api" "github.com/yaoapp/yao/app" + "github.com/yaoapp/yao/cert" "github.com/yaoapp/yao/chart" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/flow" @@ -18,11 +20,15 @@ import ( "github.com/yaoapp/yao/page" "github.com/yaoapp/yao/plugin" "github.com/yaoapp/yao/query" + "github.com/yaoapp/yao/schedule" "github.com/yaoapp/yao/script" - "github.com/yaoapp/yao/server" "github.com/yaoapp/yao/share" + "github.com/yaoapp/yao/socket" + "github.com/yaoapp/yao/store" "github.com/yaoapp/yao/table" - "github.com/yaoapp/yao/workflow" + "github.com/yaoapp/yao/task" + "github.com/yaoapp/yao/websocket" + "github.com/yaoapp/yao/widget" ) // Load 根据配置加载 API, FLow, Model, Plugin @@ -37,9 +43,15 @@ func Load(cfg config.Config) (err error) { gou.LoadCrypt(fmt.Sprintf(`{"key":"%s"}`, cfg.DB.AESKey), "AES") gou.LoadCrypt(`{}`, "PASSWORD") + // Load Certs + err = cert.Load(cfg) + if err != nil { + printErr(cfg.Mode, "Cert", err) + } + // 第二步: 建立数据库 & 会话连接 - share.DBConnect(cfg.DB) // 创建数据库连接 - share.SessionConnect(cfg.Session) // 创建会话服务器链接 + share.DBConnect(cfg.DB) // 创建数据库连接 + // share.SessionConnect(cfg.Session) // 创建会话服务器链接 // 加载应用引擎 if os.Getenv("YAO_DEV") != "" { @@ -54,51 +66,94 @@ func Load(cfg config.Config) (err error) { // 第四步: 加载共享库 & JS 处理器 err = share.Load(cfg) // 加载共享库 lib if err != nil { - log.Warn(err.Error()) + printErr(cfg.Mode, "Lib", err) } + err = script.Load(cfg) // 加载JS处理器 script if err != nil { - log.Warn(err.Error()) + printErr(cfg.Mode, "Script", err) } // 第五步: 加载数据模型等 err = model.Load(cfg) // 加载数据模型 model if err != nil { - log.Warn(err.Error()) + printErr(cfg.Mode, "Model", err) } err = flow.Load(cfg) // 加载业务逻辑 Flow if err != nil { - log.Warn(err.Error()) + printErr(cfg.Mode, "Flow", err) } + + err = store.Load(cfg) // Load stores + if err != nil { + printErr(cfg.Mode, "Store", err) + } + err = plugin.Load(cfg) // 加载业务插件 plugin if err != nil { - log.Warn(err.Error()) + printErr(cfg.Mode, "Plugin", err) } err = table.Load(cfg) // 加载数据表格 table if err != nil { - log.Warn(err.Error()) + printErr(cfg.Mode, "Table", err) } err = chart.Load(cfg) // 加载分析图表 chart if err != nil { - log.Warn(err.Error()) + printErr(cfg.Mode, "Chart", err) + } + + err = page.Load(cfg) // 加载页面 page 忽略错误 + if err != nil { + printErr(cfg.Mode, "Page", err) } - page.Load(cfg) // 加载页面 page 忽略错误 importer.Load(cfg) // 加载数据导入 imports - workflow.Load(cfg) // 加载工作流 workflow + + // workflow.Load(cfg) // 加载工作流 workflow err = api.Load(cfg) // 加载业务接口 API if err != nil { - log.Warn(err.Error()) + printErr(cfg.Mode, "API", err) + } + + err = socket.Load(cfg) // Load sockets + if err != nil { + printErr(cfg.Mode, "Socket", err) + } + + err = websocket.Load(cfg) // Load websockets (client) + if err != nil { + printErr(cfg.Mode, "WebSocket", err) + } + + err = task.Load(cfg) // Load tasks + if err != nil { + printErr(cfg.Mode, "Task", err) + } + + err = schedule.Load(cfg) // Load schedules + if err != nil { + printErr(cfg.Mode, "Schedule", err) + } + + err = widget.Load(cfg) // Load widgets + if err != nil { + printErr(cfg.Mode, "Widget", err) } - server.Load(cfg) // 加载服务 return nil } +func printErr(mode, widget string, err error) { + message := fmt.Sprintf("[%s] %s", widget, err.Error()) + if !strings.Contains(message, "does not exists") && mode == "development" { + color.Red(message) + } +} + // Reload 根据配置重新加载 API, FLow, Model, Plugin func Reload(cfg config.Config) { gou.APIs = map[string]*gou.API{} diff --git a/engine/user_test.go b/engine/user_test.go index 4a05b208..03448057 100644 --- a/engine/user_test.go +++ b/engine/user_test.go @@ -8,6 +8,7 @@ import ( ) func TestUserAuth(t *testing.T) { + res := user.Auth("email", "xiang@iqka.com", "A123456p+") assert.True(t, res.Has("user")) assert.True(t, res.Has("token")) diff --git a/flow/flow_test.go b/flow/flow_test.go index 429cb9f3..b1b06c19 100644 --- a/flow/flow_test.go +++ b/flow/flow_test.go @@ -20,5 +20,5 @@ func check(t *testing.T) { for key := range gou.Flows { keys = append(keys, key) } - assert.Equal(t, 22, len(keys)) + assert.Equal(t, 26, len(keys)) } diff --git a/go.mod b/go.mod index c41c60d8..b22f638f 100644 --- a/go.mod +++ b/go.mod @@ -1,50 +1,99 @@ module github.com/yaoapp/yao -go 1.16 +go 1.18 require ( github.com/aliyun/alibaba-cloud-sdk-go v1.61.1286 github.com/aliyun/aliyun-oss-go-sdk v2.1.10+incompatible - github.com/armon/go-metrics v0.3.10 // indirect - github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect - github.com/buraksezer/consistent v0.9.0 // indirect - github.com/buraksezer/olric v0.4.2 github.com/caarlos0/env/v6 v6.9.1 github.com/elazarl/go-bindata-assetfs v1.0.1 github.com/fatih/color v1.13.0 - github.com/fsnotify/fsnotify v1.5.1 + github.com/fsnotify/fsnotify v1.5.4 github.com/gin-gonic/gin v1.7.7 - github.com/go-sql-driver/mysql v1.6.0 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/google/btree v1.0.1 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-msgpack v1.1.5 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/golang-lru v0.5.4 // indirect - github.com/hashicorp/memberlist v0.3.0 // indirect - github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect - github.com/jmoiron/sqlx v1.3.4 // indirect github.com/joho/godotenv v1.3.0 github.com/json-iterator/go v1.1.12 - github.com/lib/pq v1.10.4 // indirect - github.com/mattn/go-sqlite3 v1.14.9 // indirect - github.com/miekg/dns v1.1.43 // indirect github.com/mojocn/base64Captcha v1.3.5 - github.com/satori/go.uuid v1.2.0 // indirect github.com/spf13/afero v1.6.0 github.com/spf13/cobra v1.2.1 - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.7.1 github.com/xuri/excelize/v2 v2.5.0 github.com/yaoapp/gou v0.0.0-20211120135538-e5387704eb03 github.com/yaoapp/kun v0.9.0 github.com/yaoapp/xun v0.9.0 golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab +) + +require ( + github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 // indirect + github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-errors/errors v1.4.2 // indirect + github.com/go-playground/locales v0.13.0 // indirect + github.com/go-playground/universal-translator v0.17.0 // indirect + github.com/go-playground/validator/v10 v10.4.1 // indirect + github.com/go-redis/redis/v8 v8.11.4 // indirect + github.com/go-sql-driver/mysql v1.6.0 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/uuid v1.3.0 + github.com/gorilla/websocket v1.5.0 // indirect + github.com/hashicorp/go-hclog v1.2.0 // indirect + github.com/hashicorp/go-plugin v1.4.4 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect + github.com/jmoiron/sqlx v1.3.4 // indirect + github.com/leodido/go-urn v1.2.0 // indirect + github.com/lib/pq v1.10.4 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-sqlite3 v1.14.9 // indirect + github.com/miekg/dns v1.1.48 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/oklog/run v1.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/richardlehane/mscfb v1.0.3 // indirect + github.com/richardlehane/msoleps v1.0.1 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/satori/go.uuid v1.2.0 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/takama/daemon v1.0.0 // indirect + github.com/tidwall/btree v1.1.0 // indirect + github.com/tidwall/buntdb v1.2.9 // indirect + github.com/tidwall/gjson v1.12.1 // indirect + github.com/tidwall/grect v0.1.4 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/tidwall/rtred v0.1.2 // indirect + github.com/tidwall/tinyqueue v0.1.1 // indirect + github.com/ugorji/go/codec v1.1.7 // indirect + github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3 // indirect golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect - golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 // indirect + golang.org/x/mod v0.4.2 // indirect + golang.org/x/net v0.0.0-20220526153639-5463443f8c37 // indirect + golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect + golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect - google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 // indirect - google.golang.org/grpc v1.42.0 // indirect + golang.org/x/tools v0.1.7 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/genproto v0.0.0-20220526192754-51939a95c655 // indirect + google.golang.org/grpc v1.46.2 // indirect + google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + gopkg.in/ini.v1 v1.62.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0 // indirect + rogchap.com/v8go v0.7.0 // indirect ) // go env -w GOPRIVATE=github.com/yaoapp/* diff --git a/go.sum b/go.sum index 971128fe..e0131174 100644 --- a/go.sum +++ b/go.sum @@ -39,15 +39,8 @@ 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= 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/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4= github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2/go.mod h1:VSw57q4QFiWDbRnjdX8Cb3Ow0SFncRw+bA/ofY6Q83w= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aliyun/alibaba-cloud-sdk-go v1.61.1286 h1:+Ah33S/QRnOFp4dM6VV2gYDbFu3Cj4HrRy1VpOhBzm8= github.com/aliyun/alibaba-cloud-sdk-go v1.61.1286/go.mod h1:9CMdKNL3ynIGPpfTcdwTvIm8SGuAZYYC4jFVSSvE1YQ= github.com/aliyun/aliyun-oss-go-sdk v2.1.10+incompatible h1:D3gwOr9qUUmyyBRDbpnATqu+EkqqmigFd3Od6xO1QUU= @@ -55,52 +48,38 @@ github.com/aliyun/aliyun-oss-go-sdk v2.1.10+incompatible/go.mod h1:T/Aws4fEfogEE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo= -github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/buraksezer/connpool v0.4.0 h1:fNLvWu0FOtJxL7Sqetm6+040mhZWVs8c6vrke14SEKw= -github.com/buraksezer/connpool v0.4.0/go.mod h1:qPiG7gKXo+EjrwG/yqn2StZM4ek6gcYnnGgFIVKN6b0= -github.com/buraksezer/consistent v0.0.0-20191006190839-693edf70fd72/go.mod h1:OEE5igu/CDjGegM1Jn6ZMo7R6LlV/JChAkjfQQIRLpg= -github.com/buraksezer/consistent v0.9.0 h1:Zfs6bX62wbP3QlbPGKUhqDw7SmNkOzY5bHZIYXYpR5g= -github.com/buraksezer/consistent v0.9.0/go.mod h1:6BrVajWq7wbKZlTOUPs/XVfR8c0maujuPowduSpZqmw= -github.com/buraksezer/olric v0.4.2 h1:1W5UCYFYrkeD6OHGyTi6R9YTLJ3vXcVkgas4ZbFQMfw= -github.com/buraksezer/olric v0.4.2/go.mod h1:xNt+/QiiVuqioJzgTMuiV2LynTNu87L1Tp5289XuU78= github.com/caarlos0/env/v6 v6.9.1 h1:zOkkjM0F6ltnQ5eBX6IPI41UP/KDGEK7rRPwGCNos8k= github.com/caarlos0/env/v6 v6.9.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc= 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/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/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/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= -github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw= github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -109,16 +88,17 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= @@ -129,27 +109,21 @@ github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3Bop github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= -github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= +github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= @@ -188,8 +162,6 @@ github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -200,8 +172,9 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -219,42 +192,30 @@ github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.1.0 h1:QsGcniKx5/LuX2eYoeL+Np3UKYPNaN7YKpTh29h8rbw= -github.com/hashicorp/go-hclog v1.1.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= +github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= -github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-msgpack v1.1.5 h1:9byZdVjKTe5mce63pRVNP1L7UAmdHOTEMGehn6KvJWs= -github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.4.2/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= -github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= -github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= -github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ= +github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -262,35 +223,26 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/memberlist v0.1.5/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/memberlist v0.3.0 h1:8+567mCcFDnS5ADl7lrpxPMWiFCElyUEeW0gtj34fMA= -github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hashicorp/yamux v0.0.0-20210707203944-259a57b3608c/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I= github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hokaccha/go-prettyjson v0.0.0-20210113012101-fb4e108d2519 h1:nqAlWFEdqI0ClbTDrhDvE/8LeQ4pftrqKUX9w5k0j3s= -github.com/hokaccha/go-prettyjson v0.0.0-20210113012101-fb4e108d2519/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= -github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -299,57 +251,42 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA= github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= -github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= -github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= +github.com/miekg/dns v1.1.48 h1:Ucfr7IIVyMBz4lRE8qmGUuZ4Wt3/ZGu9hmcMT3Uu4tQ= +github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -366,64 +303,49 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mojocn/base64Captcha v1.3.5 h1:Qeilr7Ta6eDtG4S+tQuZ5+hO+QHbiGAJdi4PfoagaA0= github.com/mojocn/base64Captcha v1.3.5/go.mod h1:/tTTXn4WTpX9CfrmipqRytCpJ27Uw3G6I7NcP2WwcmY= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +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/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= +github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/richardlehane/mscfb v1.0.3 h1:rD8TBkYWkObWO0oLDFCbwMeZ4KoalxQy+QgniCj3nKI= github.com/richardlehane/mscfb v1.0.3/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= github.com/richardlehane/msoleps v1.0.1 h1:RfrALnSNXzmXLbGct/P2b4xkFz4e8Gmj/0Vj9M9xC1o= github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= -github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f h1:a7clxaGmmqtdNTXyvrp/lVO/Gnkzlhc/+dLs5v965GM= -github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f/go.mod h1:/mK7FZ3mFYEn9zvNPhpngTyatyehSwte5bJZ4ehL5Xw= +github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= +github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= 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.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -434,24 +356,40 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 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/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/takama/daemon v1.0.0 h1:XS3VLnFKmqw2Z7fQ/dHRarrVjdir9G3z7BEP8osjizQ= +github.com/takama/daemon v1.0.0/go.mod h1:gKlhcjbqtBODg5v9H1nj5dU1a2j2GemtuWSNLD5rxOE= +github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI= +github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8= +github.com/tidwall/btree v1.1.0 h1:5P+9WU8ui5uhmcg3SoPyTwoI0mVyZ1nps7YQzTZFkYM= +github.com/tidwall/btree v1.1.0/go.mod h1:TzIRzen6yHbibdSfK6t8QimqbUnoxUSrZfeW7Uob0q4= +github.com/tidwall/buntdb v1.2.9 h1:XVz684P7X6HCTrdr385yDZWB1zt/n20ZNG3M1iGyFm4= +github.com/tidwall/buntdb v1.2.9/go.mod h1:IwyGSvvDg6hnKSIhtdZ0AqhCZGH8ukdtCAzaP8fI1X4= +github.com/tidwall/gjson v1.12.1 h1:ikuZsLdhr8Ws0IdROXUS1Gi4v9Z4pGqpX/CvJkxvfpo= +github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg= +github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q= +github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8= +github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8= +github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ= +github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE= +github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E= -github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= -github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= -github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= -github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3 h1:EpI0bqf/eX9SdZDwlMmahKM+CDBgNbsXMhsN28XrM8o= github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= github.com/xuri/excelize/v2 v2.5.0 h1:nDDVfX0qaDuGjAvb+5zTd0Bxxoqa1Ffv9B4kiE23PTM= @@ -461,7 +399,6 @@ 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.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= @@ -476,16 +413,13 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-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-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201001193750-eb9a90e9f9cb/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab h1:lnZ4LoV0UMdibeCUfIB2a4uFwRu491WX/VB2reB8xNc= golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -527,12 +461,12 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -541,11 +475,9 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-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-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= @@ -556,11 +488,11 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -569,12 +501,10 @@ 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-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 h1:DZshvxDdVoeKIbudAdFEKi+f70l51luSy/7b76ibTY0= -golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220526153639-5463443f8c37 h1:lUkvobShwKsOesNfWWlCS5q7fnbG1MEliIzwu886fn8= +golang.org/x/net v0.0.0-20220526153639-5463443f8c37/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -595,32 +525,29 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200930132711-30421366ff76/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/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-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-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-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-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -636,15 +563,16 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-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-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -654,13 +582,10 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220207234003-57398862261d h1:Bm7BNOQt2Qv7ZqysjeLjgCBanX+88Z/OtdvsrEv1Djc= -golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -684,7 +609,6 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190424220101-1e8e1cfdf96b/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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -692,7 +616,6 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -701,7 +624,6 @@ golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -727,11 +649,13 @@ 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-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= 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= @@ -766,9 +690,7 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -810,10 +732,8 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 h1:b9mVrqYfq3P4bCdaLg1qtBnPzUYgglsIdjZkL/fQVOE= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/genproto v0.0.0-20220526192754-51939a95c655 h1:56rmjc5LUAanErbiNrY+s/Nd47wDQEJkpqS7i43M1I0= +google.golang.org/genproto v0.0.0-20220526192754-51939a95c655/go.mod h1:yKyY4AMRwFiC8yMMNaMi+RkCnjZJt9LoWuvhXjMs+To= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -834,9 +754,8 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.46.2 h1:u+MLGgVf7vRdjEYZ8wDFhAVNmhkbJ5hmrA1LMWK1CAQ= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -849,32 +768,31 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/readline.v1 v1.0.0-20160726135117-62c6fe619375/go.mod h1:lNEQeAhU009zbRxng+XOj5ITVgY24WcbNnQopyfKoYQ= -gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= -gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-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.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= +gopkg.in/yaml.v3 v3.0.0/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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/helper/hex.process.go b/helper/hex.process.go new file mode 100644 index 00000000..d43be53a --- /dev/null +++ b/helper/hex.process.go @@ -0,0 +1,22 @@ +package helper + +import ( + "encoding/hex" + + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/log" +) + +// ProcessHexToString xiang.helper.HexToString +func ProcessHexToString(process *gou.Process) interface{} { + process.ValidateArgNums(1) + + switch process.Args[0].(type) { + case string: + return hex.EncodeToString([]byte(process.Args[0].(string))) + case []byte: + return hex.EncodeToString(process.Args[0].([]byte)) + } + log.With(log.F{"input": process.Args[0]}).Error("HexToString: type does not support") + return nil +} diff --git a/helper/hex_test.go b/helper/hex_test.go new file mode 100644 index 00000000..bca5e63a --- /dev/null +++ b/helper/hex_test.go @@ -0,0 +1,61 @@ +package helper + +import ( + "os" + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou" + "github.com/yaoapp/yao/flow" + "github.com/yaoapp/yao/share" +) + +func TestProcessHexToString(t *testing.T) { + res, err := gou.NewProcess("xiang.helper.HexToString", []byte{0x0, 0x1}).Exec() + assert.Nil(t, err) + assert.Equal(t, "0001", res) + + res, err = gou.NewProcess("xiang.helper.HexToString", string([]byte{0x0, 0x1})).Exec() + assert.Nil(t, err) + assert.Equal(t, "0001", res) + + res, err = gou.NewProcess("xiang.helper.HexToString", 1024).Exec() + assert.Nil(t, err) + assert.Nil(t, res) +} + +func TestProcessHexToStringInFlow(t *testing.T) { + testflow := path.Join(os.Getenv("YAO_DEV"), "tests", "flows", "helper") + flow.LoadFrom(testflow, "helper.") + + res, err := gou.NewProcess("flows.helper.HexToString").Exec() + assert.Nil(t, err) + assert.Equal(t, "6162", res) // ab +} + +func TestProcessHexToStringInScript(t *testing.T) { + testscirpt := path.Join(os.Getenv("YAO_DEV"), "tests", "scripts") + share.LoadFrom(testscirpt) + + res, err := gou.NewProcess("scripts.helper.HexToStringString").Exec() + assert.Nil(t, err) + assert.Equal(t, "6162", res) // ab + + res, err = gou.NewProcess("scripts.helper.HexToStringBytes", []byte{0x0, 0x1}).Exec() + assert.Nil(t, err) + assert.Equal(t, "0001", res) // []byte{0x0, 0x1} +} + +func TestProcessBufferInScript(t *testing.T) { + testscirpt := path.Join(os.Getenv("YAO_DEV"), "tests", "scripts") + share.LoadFrom(testscirpt) + + res, err := gou.NewProcess("scripts.helper.Buffer").Exec() + assert.Nil(t, err) + + str, ok := res.(string) + assert.True(t, ok) + assert.Equal(t, []byte{0x0, 0x1}, []byte(str)) + +} diff --git a/helper/process.go b/helper/process.go index 73d43e24..7de681f8 100644 --- a/helper/process.go +++ b/helper/process.go @@ -27,6 +27,8 @@ func init() { gou.RegisterProcessHandler("xiang.helper.MapDel", ProcessMapDel) gou.RegisterProcessHandler("xiang.helper.MapMultiDel", ProcessMapMultiDel) + gou.RegisterProcessHandler("xiang.helper.HexToString", ProcessHexToString) + gou.RegisterProcessHandler("xiang.helper.StrConcat", ProcessStrConcat) gou.RegisterProcessHandler("xiang.helper.Captcha", ProcessCaptcha) @@ -64,6 +66,7 @@ func init() { gou.RegisterProcessHandler("xiang.flow.Sleep", ProcessSleep) gou.AliasProcess("xiang.flow.Sleep", "xiang.sys.Sleep") + } // ProcessPrint xiang.helper.Print 打印语句 diff --git a/importer/importer.go b/importer/importer.go index 30b594b7..5dca3bf0 100644 --- a/importer/importer.go +++ b/importer/importer.go @@ -7,6 +7,7 @@ import ( "sort" "strings" + "github.com/google/uuid" jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou" "github.com/yaoapp/kun/any" @@ -382,19 +383,22 @@ func (imp *Importer) SaveAsTemplate(src from.Source) { } // Run 运行导入 -func (imp *Importer) Run(src from.Source, mapping *Mapping) map[string]int { +func (imp *Importer) Run(src from.Source, mapping *Mapping) interface{} { if mapping == nil { mapping = imp.AutoMapping(src) } + id := uuid.NewString() + page := 0 total := 0 failed := 0 ignore := 0 imp.Chunk(src, mapping, func(line int, data [][]interface{}) { + page++ length := len(data) total = total + length columns, data := imp.DataClean(data, mapping.Columns) - process, err := gou.ProcessOf(imp.Process, columns, data) + process, err := gou.ProcessOf(imp.Process, columns, data, id, page) if err != nil { failed = failed + length log.With(log.F{"line": line}).Error("导入失败: %s", err.Error()) @@ -424,12 +428,24 @@ func (imp *Importer) Run(src from.Source, mapping *Mapping) map[string]int { log.With(log.F{"line": line, "response": response, "length": length}).Error("导入处理器未返回失败结果") }) - return map[string]int{ + + output := map[string]int{ "total": total, "success": total - failed - ignore, "failure": failed, "ignore": ignore, } + + if imp.Output != "" { + res, err := gou.NewProcess(imp.Output, output).Exec() + if err != nil { + log.With(log.F{"output": imp.Output}).Error(err.Error()) + return output + } + return res + } + + return output } // Start 运行导入(异步) diff --git a/importer/importer_test.go b/importer/importer_test.go index 540aa173..d9d13184 100644 --- a/importer/importer_test.go +++ b/importer/importer_test.go @@ -89,7 +89,7 @@ func TestRunSimple(t *testing.T) { imp := Select("order") mapping := imp.AutoMapping(file) - res := imp.Run(file, mapping) + res := imp.Run(file, mapping).(map[string]int) assert.Equal(t, 1, res["ignore"]) assert.Equal(t, 1, res["failure"]) assert.Equal(t, 2, res["success"]) diff --git a/importer/types.go b/importer/types.go index a574a537..db0ca322 100644 --- a/importer/types.go +++ b/importer/types.go @@ -13,6 +13,7 @@ const PreviewNever = "never" type Importer struct { Title string `json:"title,omitempty"` // 导入名称 Process string `json:"process"` // 处理器名称 + Output string `json:"output,omitempty"` // The process import output Columns []Column `json:"columns"` // 字段列表 Option Option `json:"option,omitempty"` // 导入配置项 Rules map[string]string `json:"rules,omitempty"` // 许可导入规则 diff --git a/importer/xlsx/xlsx.go b/importer/xlsx/xlsx.go index 378adcaa..5b2ee80c 100644 --- a/importer/xlsx/xlsx.go +++ b/importer/xlsx/xlsx.go @@ -95,7 +95,7 @@ func (xlsx *Xlsx) Chunk(size int, axises []string, cb func(line int, data [][]in row, end := xlsx.readLine(line, axises) if end { cb(line, data) - break + return } data = append(data, row) diff --git a/lang/lang.go b/lang/lang.go new file mode 100644 index 00000000..ddf950f1 --- /dev/null +++ b/lang/lang.go @@ -0,0 +1,38 @@ +package lang + +import ( + "os" + "path/filepath" + + "github.com/yaoapp/gou/lang" + "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/config" +) + +func init() { + lang.RegisterWidget("tables", "table") + lang.RegisterWidget("charts", "chart") + lang.RegisterWidget("kanban", "page") + lang.RegisterWidget("screen", "page") + lang.RegisterWidget("pages", "page") +} + +// Load language packs +func Load(cfg config.Config) error { + root := filepath.Join(cfg.Root, "langs") + err := lang.Load(root) + if err != nil { + return err + } + + name := os.Getenv("YAO_LANG") + if name != "" { + if _, has := lang.Dicts[name]; !has { + log.Error("The language pack %s does not found", name) + return nil + } + lang.Pick(name).AsDefault() + } + + return nil +} diff --git a/lang/lang_test.go b/lang/lang_test.go new file mode 100644 index 00000000..68763869 --- /dev/null +++ b/lang/lang_test.go @@ -0,0 +1,16 @@ +package lang + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou/lang" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/share" +) + +func TestLoad(t *testing.T) { + share.DBConnect(config.Conf.DB) + Load(config.Conf) + assert.Len(t, lang.Dicts, 2) +} diff --git a/main.go b/main.go index 98509fdf..64609582 100644 --- a/main.go +++ b/main.go @@ -2,8 +2,12 @@ package main import ( "github.com/yaoapp/yao/cmd" + + _ "github.com/yaoapp/gou/encoding" + _ "github.com/yaoapp/yao/crypto" _ "github.com/yaoapp/yao/helper" _ "github.com/yaoapp/yao/network" + _ "github.com/yaoapp/yao/system" _ "github.com/yaoapp/yao/user" _ "github.com/yaoapp/yao/xfs" ) diff --git a/main_test.go b/main_test.go index e5c2a25a..8a00a058 100644 --- a/main_test.go +++ b/main_test.go @@ -122,6 +122,7 @@ func TestCommandStop(t *testing.T) { times++ res, err := request() if err != nil { + fmt.Println("REQUEST ERROR:", err) continue } assert.Equal(t, 1, any.Of(res.Get("id")).CInt()) diff --git a/model/model.go b/model/model.go index f4fa6f56..e71e8425 100644 --- a/model/model.go +++ b/model/model.go @@ -5,6 +5,7 @@ import ( "path/filepath" "github.com/yaoapp/gou" + "github.com/yaoapp/gou/lang" "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/share" @@ -31,7 +32,14 @@ func LoadFrom(dir string, prefix string) error { _, err := gou.LoadModelReturn(string(content), name) if err != nil { log.With(log.F{"root": root, "file": filename}).Error(err.Error()) + return } + + // Apply the language pack + if lang.Default != nil { + lang.Default.Apply(gou.Models[name]) + } + }) return err diff --git a/model/model_test.go b/model/model_test.go index 52a485b3..02df055f 100644 --- a/model/model_test.go +++ b/model/model_test.go @@ -1,17 +1,23 @@ package model import ( + "os" "testing" "github.com/stretchr/testify/assert" "github.com/yaoapp/gou" "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/lang" "github.com/yaoapp/yao/share" ) func TestLoad(t *testing.T) { + + os.Setenv("YAO_LANG", "zh-hk") + lang.Load(config.Conf) share.DBConnect(config.Conf.DB) + gou.Models = make(map[string]*gou.Model) Load(config.Conf) LoadFrom("not a path", "404.") @@ -23,5 +29,9 @@ func check(t *testing.T) { for key := range gou.Models { keys = append(keys, key) } - assert.Equal(t, 10, len(keys)) + assert.Equal(t, 11, len(keys)) + + demo := gou.Select("demo") + assert.Equal(t, demo.MetaData.Name, "演示") + assert.Equal(t, demo.Columns["action"].Label, "動作") } diff --git a/network/ip.go b/network/ip.go index 6f1243f3..80f1fc55 100644 --- a/network/ip.go +++ b/network/ip.go @@ -41,7 +41,7 @@ func ProcessIP(process *gou.Process) interface{} { // FreePort xiang.network.FreePort 获取可用端口 func FreePort() int { - addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + addr, err := net.ResolveTCPAddr("tcp", ":0") if err != nil { exception.New("获取可用端口失败 %s", 500, err.Error()).Throw() return 0 @@ -52,6 +52,7 @@ func FreePort() int { exception.New("获取可用端口失败 %s", 500, err.Error()).Throw() return 0 } + defer l.Close() return l.Addr().(*net.TCPAddr).Port } diff --git a/network/request.go b/network/request.go index 3e8d23c8..41db5a52 100644 --- a/network/request.go +++ b/network/request.go @@ -9,6 +9,7 @@ import ( "strings" jsoniter "github.com/json-iterator/go" + "github.com/yaoapp/gou/dns" ) // Response 请求响应结果 @@ -94,16 +95,21 @@ func RequestSend(method string, url string, params map[string]interface{}, data } } - // Https - var client *http.Client = &http.Client{} + // Force using system DSN resolver + // var dialer = &net.Dialer{Resolver: &net.Resolver{PreferGo: false}} + var dialContext = dns.DialContext() + var tr = &http.Transport{DialContext: dialContext} + var client *http.Client = &http.Client{Transport: tr} - // SkipVerify false + // Https SkipVerify false if strings.HasPrefix(url, "https://") { - tr := &http.Transport{ + tr = &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + DialContext: dialContext, } client = &http.Client{Transport: tr} } + defer tr.CloseIdleConnections() resp, err := client.Do(req) if err != nil { diff --git a/page/lang.go b/page/lang.go new file mode 100644 index 00000000..9342eabe --- /dev/null +++ b/page/lang.go @@ -0,0 +1,125 @@ +package page + +// Lang for applying a language pack +func (page *Page) Lang(trans func(widget string, inst string, value *string) bool) { + inst := page.Flow.Name + widget := "page" + + trans(widget, inst, &page.Name) + trans(widget, inst, &page.Label) + trans(widget, inst, &page.Description) + trans(widget, inst, &page.Page.Primary) + transMap(widget, inst, page.Page.Layout, trans) + page.Output = transAny(widget, inst, page.Output, trans) + + // Filters + for name, filter := range page.Filters { + new := name + trans(widget, inst, &new) + trans(widget, inst, &filter.Label) + delete(page.Filters, name) + + // Props + transMap(widget, inst, filter.Input.Props, trans) + page.Filters[new] = filter + } + +} + +func transAny(widget string, inst string, input interface{}, trans func(widget string, inst string, value *string) bool) interface{} { + switch input.(type) { + case []interface{}: + values := input.([]interface{}) + transArr(widget, inst, values, trans) + input = values + break + + case map[string]interface{}: + values := input.(map[string]interface{}) + for name, value := range values { + new := name + newValue := value + + switch value.(type) { + case string: + val := value.(string) + trans(widget, inst, &val) + newValue = val + break + + case []interface{}: + vals := value.([]interface{}) + transArr(widget, inst, vals, trans) + newValue = vals + break + + case map[string]interface{}: + vals := value.(map[string]interface{}) + transMap(widget, inst, vals, trans) + newValue = vals + break + } + + trans(widget, inst, &new) + delete(values, name) + values[new] = newValue + } + input = values + break + } + + return input +} + +func transMap(widget string, inst string, values map[string]interface{}, trans func(widget string, inst string, value *string) bool) { + for key, value := range values { + + switch value.(type) { + + case string: + v := value.(string) + trans(widget, inst, &v) + values[key] = v + break + + case []interface{}: + v := value.([]interface{}) + transArr(widget, inst, v, trans) + values[key] = v + break + + case map[string]interface{}: + v := value.(map[string]interface{}) + transMap(widget, inst, v, trans) + values[key] = v + break + } + + } +} + +func transArr(widget string, inst string, values []interface{}, trans func(widget string, inst string, value *string) bool) { + for key, value := range values { + switch value.(type) { + + case string: + v := value.(string) + trans(widget, inst, &v) + values[key] = v + break + + case []interface{}: + v := value.([]interface{}) + transArr(widget, inst, v, trans) + values[key] = v + break + + case map[string]interface{}: + v := value.(map[string]interface{}) + transMap(widget, inst, v, trans) + values[key] = v + break + } + + } +} diff --git a/page/page.go b/page/page.go index 8cbe9efb..98bbfe15 100644 --- a/page/page.go +++ b/page/page.go @@ -6,6 +6,7 @@ import ( jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou" + "github.com/yaoapp/gou/lang" "github.com/yaoapp/kun/exception" "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/config" @@ -24,15 +25,15 @@ func Load(cfg config.Config) error { var errs error = nil if err := LoadFrom(path.Join(cfg.Root, "/kanban"), ""); err != nil { - log.Trace("load kanban error: %s ", err.Error()) + // log.Trace("load kanban error: %s ", err.Error()) errs = err } if err := LoadFrom(path.Join(cfg.Root, "/screen"), ""); err != nil { - log.Trace("load screen error: %s", err.Error()) + // log.Trace("load screen error: %s", err.Error()) errs = err } if err := LoadFrom(path.Join(cfg.Root, "/pages"), ""); err != nil { - log.Trace("load screen error: %s", err.Error()) + // log.Trace("load screen error: %s", err.Error()) errs = err } @@ -93,6 +94,12 @@ func LoadPage(source []byte, name string) (*Page, error) { page.Prepare() page.SetupAPIs() Pages[name] = page + + // Apply a language pack + if lang.Default != nil { + lang.Default.Apply(Pages[name]) + } + return page, nil } diff --git a/page/page_test.go b/page/page_test.go index 652474c1..8aa90262 100644 --- a/page/page_test.go +++ b/page/page_test.go @@ -1,16 +1,22 @@ package page import ( + "os" "testing" "github.com/stretchr/testify/assert" "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/lang" "github.com/yaoapp/yao/model" "github.com/yaoapp/yao/query" "github.com/yaoapp/yao/share" ) func TestLoad(t *testing.T) { + + os.Setenv("YAO_LANG", "zh-hk") + lang.Load(config.Conf) + share.DBConnect(config.Conf.DB) model.Load(config.Conf) query.Load(config.Conf) @@ -25,5 +31,15 @@ func check(t *testing.T) { for key := range Pages { keys = append(keys, key) } - assert.Equal(t, 1, len(keys)) + assert.Equal(t, 2, len(keys)) + + lang := Pages["lang"] + output := lang.Output.(map[string]interface{}) + assert.Equal(t, "{{$in}}", output["參數"]) + + filters := lang.Page.Layout["filters"].([]interface{}) + begin := filters[0].(map[string]interface{}) + assert.Equal(t, "開始時間", begin["name"]) + + assert.Equal(t, "請選擇開始時間", lang.Filters["開始時間"].Input.Props["placeholder"]) } diff --git a/page/process.go b/page/process.go index 27dfc1f1..21eb2e8e 100644 --- a/page/process.go +++ b/page/process.go @@ -33,6 +33,10 @@ func ProcessData(process *gou.Process) interface{} { } } + // with Session + page.Flow.WithGlobal(process.Global) + page.Flow.WithSID(process.Sid) + return page.GetData(params) } diff --git a/page/process_test.go b/page/process_test.go index 0065567f..ef56b914 100644 --- a/page/process_test.go +++ b/page/process_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/yaoapp/gou" "github.com/yaoapp/kun/any" - "github.com/yaoapp/kun/utils" "github.com/yaoapp/yao/config" _ "github.com/yaoapp/yao/helper" "github.com/yaoapp/yao/model" @@ -68,8 +67,6 @@ func TestProcessData(t *testing.T) { } process := gou.NewProcess("xiang.page.Data", args...) response := ProcessData(process) - utils.Dump(response) - assert.NotNil(t, response) res := any.Of(response).Map().Dot() diff --git a/query/query.go b/query/query.go index 79f958d5..9039532c 100644 --- a/query/query.go +++ b/query/query.go @@ -2,6 +2,7 @@ package query import ( "github.com/yaoapp/gou" + "github.com/yaoapp/gou/query" dsl "github.com/yaoapp/gou/query/gou" "github.com/yaoapp/kun/log" "github.com/yaoapp/xun/capsule" @@ -10,21 +11,21 @@ import ( // Load 加载查询引擎 func Load(cfg config.Config) { - XiangQuery() + DefaultQuery() } -// XiangQuery 注册应用引擎象 xiang -func XiangQuery() { - gou.RegisterEngine("xiang", &dsl.Query{ +// DefaultQuery register the default engine +func DefaultQuery() { + query.Register("default", &dsl.Query{ Query: capsule.Query(), GetTableName: func(s string) string { if mod, has := gou.Models[s]; has { return mod.MetaData.Table.Name } - // exception.New("%s 数据模型尚未加载", 404).Throw() log.Error("%s model does not load", s) return s }, AESKey: config.Conf.DB.AESKey, }) + query.Alias("default", "xiang") } diff --git a/schedule/schedule.go b/schedule/schedule.go new file mode 100644 index 00000000..70fe869e --- /dev/null +++ b/schedule/schedule.go @@ -0,0 +1,36 @@ +package schedule + +import ( + "fmt" + "path/filepath" + + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/share" +) + +// Load load schedule +func Load(cfg config.Config) error { + var root = filepath.Join(cfg.Root, "schedules") + return LoadFrom(root, "") +} + +// LoadFrom load from dir +func LoadFrom(dir string, prefix string) error { + + if share.DirNotExists(dir) { + return fmt.Errorf("%s does not exists", dir) + } + + err := share.Walk(dir, ".json", func(root, filename string) { + name := prefix + share.SpecName(root, filename) + content := share.ReadFile(filename) + _, err := gou.LoadSchedule(string(content), name) + if err != nil { + log.With(log.F{"root": root, "file": filename}).Error(err.Error()) + } + }) + + return err +} diff --git a/schedule/schedule_test.go b/schedule/schedule_test.go new file mode 100644 index 00000000..d0f27e3e --- /dev/null +++ b/schedule/schedule_test.go @@ -0,0 +1,21 @@ +package schedule + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/task" +) + +func TestLoad(t *testing.T) { + task.Load(config.Conf) + Load(config.Conf) + LoadFrom("not a path", "404.") + check(t) +} + +func check(t *testing.T) { + assert.Equal(t, 2, len(gou.Schedules)) +} diff --git a/service/guard.go b/service/guard.go index ce187a3a..63a3d16a 100644 --- a/service/guard.go +++ b/service/guard.go @@ -5,12 +5,14 @@ import ( "github.com/gin-gonic/gin" "github.com/yaoapp/yao/helper" + "github.com/yaoapp/yao/table" ) // Guards 服务中间件 var Guards = map[string]gin.HandlerFunc{ "bearer-jwt": bearerJWT, // JWT 鉴权 "cross-domain": crossDomain, // 跨域许可 + "table-guard": table.Guard, // Table Guard } // JWT 鉴权 @@ -25,7 +27,6 @@ func bearerJWT(c *gin.Context) { claims := helper.JwtValidate(tokenString) c.Set("__sid", claims.SID) - c.Next() } // crossDomain 跨域访问 @@ -39,6 +40,4 @@ func crossDomain(c *gin.Context) { c.AbortWithStatus(204) return } - - c.Next() } diff --git a/service/middleware.go b/service/middleware.go index ed2e746b..fe98f2f6 100644 --- a/service/middleware.go +++ b/service/middleware.go @@ -27,7 +27,8 @@ func BinStatic(c *gin.Context) { length := len(c.Request.URL.Path) - if length >= 5 && c.Request.URL.Path[0:5] == "/api/" { // API接口 + if (length >= 5 && c.Request.URL.Path[0:5] == "/api/") || + (length >= 11 && c.Request.URL.Path[0:11] == "/websocket/") { // API & websocket c.Next() return } else if length >= 7 && c.Request.URL.Path[0:7] == "/xiang/" { // 数据管理后台 diff --git a/service/service.go b/service/service.go index 304d1e1b..cec86fbd 100644 --- a/service/service.go +++ b/service/service.go @@ -1,32 +1,39 @@ package service import ( + "context" + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/share" ) -var shutdown = make(chan bool) -var shutdownComplete = make(chan bool) +var shutdown = make(chan bool, 1) +var shutdownComplete = make(chan bool, 1) // Start 启动服务 -func Start() { +func Start() error { - if config.Conf.Session.Hosting && config.Conf.Session.IsCLI == false { - share.SessionServerStart() + err := share.SessionStart() + if err != nil { + return err } gou.SetHTTPGuards(Guards) gou.ServeHTTP( gou.Server{ - Host: config.Conf.Host, - Port: config.Conf.Port, - Root: "/api", + Host: config.Conf.Host, + Port: config.Conf.Port, + Root: "/api", + Allows: config.Conf.AllowFrom, }, - &shutdown, func(s gou.Server) { + shutdown, func(s gou.Server) { shutdownComplete <- true }, Middlewares...) + + return nil } // StartWithouttSession 启动服务 @@ -35,11 +42,12 @@ func StartWithouttSession() { gou.SetHTTPGuards(Guards) gou.ServeHTTP( gou.Server{ - Host: config.Conf.Host, - Port: config.Conf.Port, - Root: "/api", + Host: config.Conf.Host, + Port: config.Conf.Port, + Root: "/api", + Allows: config.Conf.AllowFrom, }, - &shutdown, func(s gou.Server) { + shutdown, func(s gou.Server) { shutdownComplete <- true }, Middlewares...) @@ -48,16 +56,31 @@ func StartWithouttSession() { // StopWithouttSession 关闭服务 func StopWithouttSession(onComplete func()) { shutdown <- true - <-shutdownComplete - gou.KillPlugins() - onComplete() + select { + case <-shutdownComplete: + onComplete() + } } // Stop 关闭服务 func Stop(onComplete func()) { shutdown <- true - <-shutdownComplete - share.SessionServerStop() - gou.KillPlugins() - onComplete() + select { + case <-shutdownComplete: + share.SessionStop() + onComplete() + } +} + +// StopWithContext stop with timeout +func StopWithContext(ctx context.Context, onComplete func()) { + shutdown <- true + select { + case <-ctx.Done(): + log.Error("[STOP] canceled (%v)", ctx.Err()) + onComplete() + case <-shutdownComplete: + share.SessionStop() + onComplete() + } } diff --git a/service/watch.go b/service/watch.go index 92247d3f..6d3ecafe 100644 --- a/service/watch.go +++ b/service/watch.go @@ -1,459 +1,268 @@ package service import ( + "context" "fmt" + "io/fs" + "io/ioutil" "os" + "os/signal" "path/filepath" "strings" + "syscall" + "time" "github.com/fatih/color" + "github.com/fsnotify/fsnotify" "github.com/yaoapp/gou" - "github.com/yaoapp/yao/chart" + "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/engine" - "github.com/yaoapp/yao/page" "github.com/yaoapp/yao/share" - "github.com/yaoapp/yao/table" - "github.com/yaoapp/yao/workflow" ) -// Watch 监听应用目录文件变更 -func Watch(cfg config.Config) { - if os.Getenv("YAO_DEV") != "" { - WatchEngine(filepath.Join(os.Getenv("YAO_DEV"), "/yao")) - } - WatchModel(filepath.Join(cfg.Root, "models"), "") - WatchAPI(filepath.Join(cfg.Root, "apis"), "") - WatchFlow(filepath.Join(cfg.Root, "flows"), "") - WatchPlugin(filepath.Join(cfg.Root, "plugins")) - WatchTable(filepath.Join(cfg.Root, "tables"), "") - WatchChart(filepath.Join(cfg.Root, "charts"), "") - WatchPage(filepath.Join(cfg.Root, "pages"), "") - WatchWorkFlow(filepath.Join(cfg.Root, "workflows"), "") - - // 看板大屏 - WatchPage(filepath.Join(cfg.Root, "kanban"), "") - WatchPage(filepath.Join(cfg.Root, "screen"), "") - - // 监听脚本 & libs更新 - WatchGlobal(filepath.Join(cfg.Root, "libs")) - WatchGlobal(filepath.Join(cfg.Root, "scripts")) +var watchShutdown = make(chan bool, 1) // shutdown signal +var watchReady = make(chan bool, 1) // ready signal +var excludes = map[string]bool{"ui": true, "db": true, "data": true} +var handlers = map[string]func(root string, file string, event string, cfg config.Config){ + "models": watchModel, } -// WatchEngine 监听监听引擎内建数据变更 -func WatchEngine(root string) { - root = share.DirAbs(root) - WatchModel(filepath.Join(root, "models"), "xiang.") - WatchAPI(filepath.Join(root, "apis"), "xiang.") - WatchFlow(filepath.Join(root, "flows"), "xiang.") - WatchTable(filepath.Join(root, "tables"), "xiang.") +// Watch the application code change for hot update +func Watch(cfg config.Config) (err error) { + go func() { err = watchStart(cfg) }() + select { + case <-watchReady: + return nil + } } -// WatchGlobal 监听通用程序更新 -func WatchGlobal(root string) { - if share.DirNotExists(root) { - return +// StopWatch stop watching the code change +func StopWatch() { + watchShutdown <- true + time.Sleep(200 * time.Millisecond) +} + +func watchStart(cfg config.Config) error { + + root := cfg.Root + + // recive interrupt signal + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) + + shutdown := make(chan bool, 1) + + watcher, err := fsnotify.NewWatcher() + if err != nil { + return err } - root = share.DirAbs(root) - go share.Watch(root, func(op string, filename string) { - if !strings.HasSuffix(filename, ".json") && !strings.HasSuffix(filename, ".js") { - return + defer watcher.Close() + + root, err = filepath.Abs(root) + if err != nil { + return err + } + + dirs, err := ioutil.ReadDir(root) + if err != nil { + return err + } + + // Add path + for _, dir := range dirs { + if !dir.IsDir() { + continue } - // 重启服务器 - if op == "write" || op == "create" || op == "remove" || op == "rename" { - err := engine.Load(config.Conf) + name := dir.Name() + if _, has := excludes[name]; has { + continue + } + + if strings.HasPrefix(name, ".") { + continue + } + + filename := filepath.Join(root, name) + err := watcher.Add(filename) + if err != nil { + log.Error("[Watch] %s", err.Error()) + return err + } + + fmt.Println(color.GreenString("[Watch] Watching %s", name)) + log.Info("[Watch] Watching: %s", filename) + + // sub dir + depth := 0 + err = filepath.WalkDir(filename, func(path string, d fs.DirEntry, err error) error { + depth = depth + 1 + if depth == 1 { + return nil + } + + if !d.IsDir() { + return nil + } + + log.Info("[Watch] Watching: %s", path) + err = watcher.Add(path) if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return + log.Error("[Watch] %s", err.Error()) } - StopWithouttSession(func() { - fmt.Println(color.GreenString("Service Restarted")) - go StartWithouttSession() - }) - } - }) -} + return nil + }) -// WatchModel 监听业务接口更新 -func WatchModel(root string, prefix string) { - if share.DirNotExists(root) { - return + if err != nil { + log.Error("[Watch] %s", err.Error()) + } } - root = share.DirAbs(root) - go share.Watch(root, func(op string, filename string) { - if !strings.HasSuffix(filename, ".json") { - return - } - if op == "write" || op == "create" { - name := prefix + share.SpecName(root, filename) - content := share.ReadFile(filename) - _, err := gou.LoadModelReturn(string(content), name) // Reload - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) + // event handler + go func() { + for { + select { + case <-shutdown: + log.Info("[Watch] The event handler exit") return - } - fmt.Println(color.GreenString("Model %s Reloaded", name)) - } else if op == "remove" || op == "rename" { - name := prefix + share.SpecName(root, filename) - if _, has := gou.Models[name]; has { - delete(gou.Models, name) - fmt.Println(color.RedString("Model %s Removed", name)) - } - } - }) -} -// WatchAPI 监听业务接口更新 -func WatchAPI(root string, prefix string) { - if share.DirNotExists(root) { - return - } - root = share.DirAbs(root) - go share.Watch(root, func(op string, filename string) { + case event, ok := <-watcher.Events: + if !ok { + return + } + relpath := strings.TrimPrefix(event.Name, root) + if strings.HasPrefix(relpath, string(os.PathSeparator)) { + relpath = strings.TrimPrefix(relpath, string(os.PathSeparator)) + } - if !strings.HasSuffix(filename, ".json") { - return - } + pi := strings.Split(relpath, string(os.PathSeparator)) + widget := pi[0] - if op == "write" || op == "create" { - name := prefix + share.SpecName(root, filename) - content := share.ReadFile(filename) - _, err := gou.LoadAPIReturn(string(content), name) // Reload - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - fmt.Println(color.GreenString("API %s Reloaded", name)) + watchHanler := watchReload + if handler, has := handlers[widget]; has { + watchHanler = handler + } - } else if op == "remove" || op == "rename" { - name := prefix + share.SpecName(root, filename) - if _, has := gou.APIs[name]; has { - delete(gou.APIs, name) - fmt.Println(color.RedString("API %s Removed", name)) - } - } - - // 重启服务器 - if op == "write" || op == "create" || op == "remove" || op == "rename" { - StopWithouttSession(func() { - fmt.Println(color.GreenString("Service Restarted")) - go StartWithouttSession() - }) - } - }) -} - -// WatchFlow 监听业务逻辑变更 -func WatchFlow(root string, prefix string) { - if share.DirNotExists(root) { - return - } - root = share.DirAbs(root) - go share.Watch(root, func(op string, filename string) { - if !strings.HasSuffix(filename, ".json") && !strings.HasSuffix(filename, ".js") { - return - } - - if strings.HasSuffix(filename, ".js") { - name := prefix + share.SpecName(root, filename) - name = strings.ReplaceAll(name, ".", "/") - filename = filepath.Join(root, name+".flow.json") - } - - if op == "write" || op == "create" { - name := prefix + share.SpecName(root, filename) - content := share.ReadFile(filename) - flow, err := gou.LoadFlowReturn(string(content), name) // Reload - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - - if flow != nil { // Reload Script - dir := filepath.Dir(filename) - share.Walk(dir, ".js", func(root, filename string) { - script := share.ScriptName(filename) - content := share.ReadFile(filename) - _, err := flow.LoadScriptReturn(string(content), script) - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return + if _, has := excludes[widget]; !has { + base := filepath.Base(event.Name) + isdir := true + if strings.HasSuffix(base, ".yao") || strings.HasSuffix(base, ".json") || strings.HasSuffix(base, ".js") { + isdir = false } - }) - } - fmt.Println(color.GreenString("Flow %s Reloaded", name)) + events := strings.Split(event.Op.String(), "|") + for _, eventType := range events { - } else if op == "remove" || op == "rename" { - name := prefix + share.SpecName(root, filename) - if _, has := gou.Flows[name]; has { - delete(gou.Flows, name) - fmt.Println(color.RedString("Flow %s Removed", name)) - } - } - }) -} + // ADD / REMOVE Watching dir + if isdir { + switch eventType { + case "CREATE": + log.Info("[Watch] Watching: %s", event.Name) + watcher.Add(event.Name) + break + case "REMOVE": + log.Info("[Watch] Unwatching: %s", event.Name) + watcher.Remove(event.Name) + break + } + continue + } -// WatchPlugin 监听业务插件变更 -func WatchPlugin(root string) { - if share.DirNotExists(root) { - return - } - root = share.DirAbs(root) - go share.Watch(root, func(op string, filename string) { - - if !strings.HasSuffix(filename, ".so") { - return - } - - if op == "write" || op == "create" { - name := share.SpecName(root, filename) - _, err := gou.LoadPluginReturn(filename, name) // Reload - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - fmt.Println(color.GreenString("Plugin %s Reloaded", name)) - - } else if op == "remove" || op == "rename" { - name := share.SpecName(root, filename) - if _, has := gou.Plugins[name]; has { - delete(gou.Plugins, name) - fmt.Println(color.RedString("Plugin %s Removed", name)) - } - } - }) -} - -// WatchTable 监听数据表格更新 -func WatchTable(root string, prefix string) { - if share.DirNotExists(root) { - return - } - root = share.DirAbs(root) - go share.Watch(root, func(op string, filename string) { - - if !strings.HasSuffix(filename, ".json") { - return - } - - if op == "write" || op == "create" { - name := prefix + share.SpecName(root, filename) - content := share.ReadFile(filename) - _, err := table.LoadTable(string(content), name) // Reload Table - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - - api, has := gou.APIs["xiang.table"] - if has { - _, err := gou.LoadAPIReturn(api.Source, api.Name) - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - } - fmt.Println(color.GreenString("Table %s Reloaded", name)) - - } else if op == "remove" || op == "rename" { - name := prefix + share.SpecName(root, filename) - if _, has := table.Tables[name]; has { - delete(table.Tables, name) - fmt.Println(color.RedString("Table %s Removed", name)) - } - } - - // 重启服务器 - if op == "write" || op == "create" || op == "remove" || op == "rename" { - StopWithouttSession(func() { - fmt.Println(color.GreenString("Service Restarted")) - go StartWithouttSession() - }) - } - }) -} - -// WatchChart 监听分析图表更新 -func WatchChart(root string, prefix string) { - if share.DirNotExists(root) { - return - } - root = share.DirAbs(root) - go share.Watch(root, func(op string, filename string) { - if !strings.HasSuffix(filename, ".json") && !strings.HasSuffix(filename, ".js") { - return - } - - if strings.HasSuffix(filename, ".js") { - name := prefix + share.SpecName(root, filename) - name = strings.ReplaceAll(name, ".", "/") - filename = filepath.Join(root, name+".chart.json") - } - - if op == "write" || op == "create" { - name := prefix + share.SpecName(root, filename) - content := share.ReadFile(filename) - chart, err := chart.LoadChart(content, name) // Relaod - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - - if chart != nil { // Reload Script - dir := filepath.Dir(filename) - share.Walk(dir, ".js", func(root, filename string) { - script := share.ScriptName(filename) - content := share.ReadFile(filename) - _, err := chart.LoadScriptReturn(string(content), script) - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return + log.Info("[Watch] %s %s", eventType, event.Name) + watchHanler(root, event.Name, eventType, cfg) } - }) - } + } + break - api, has := gou.APIs["xiang.chart"] - if has { - _, err := gou.LoadAPIReturn(api.Source, api.Name) - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) + case err, ok := <-watcher.Errors: + if !ok { return } - } - fmt.Println(color.GreenString("Chart %s Reloaded", name)) - - } else if op == "remove" || op == "rename" { - name := prefix + share.SpecName(root, filename) - if _, has := chart.Charts[name]; has { - delete(chart.Charts, name) - fmt.Println(color.RedString("Chart %s Removed", name)) + fmt.Println(color.RedString("[Watch] %s", err.Error())) + log.Error("[Watch] %s", err.Error()) + break } } + }() - // 重启服务器 - if op == "write" || op == "create" || op == "remove" || op == "rename" { - StopWithouttSession(func() { - fmt.Println(color.GreenString("Service Restarted")) - go StartWithouttSession() - }) + fmt.Println(color.GreenString("[Watch] Started")) + watchReady <- true + + for { + select { + case <-watchShutdown: + shutdown <- true + log.Info("[Watch] Stopped") + fmt.Println(color.YellowString("[Watch] Stopped")) + return nil + + case <-interrupt: + shutdown <- true + log.Info("[Watch] Stopped") + fmt.Println(color.YellowString("[Watch] Stopped")) + return nil } - }) + } } -// WatchPage 监听页面更新 -func WatchPage(root string, prefix string) { - if share.DirNotExists(root) { - return - } - root = share.DirAbs(root) - go share.Watch(root, func(op string, filename string) { - if !strings.HasSuffix(filename, ".json") && !strings.HasSuffix(filename, ".js") { +func watchModel(root string, file string, event string, cfg config.Config) { + name := share.SpecName(root, file) + switch event { + case "CREATE": + content, err := ioutil.ReadFile(file) + if err != nil { + fmt.Println(color.RedString("[Watch] Model: %s %s", name, err.Error())) + return + } + mod, err := gou.LoadModelReturn(string(content), name) + if err != nil { + fmt.Println(color.RedString("[Watch] Model: %s %s", name, err.Error())) return } - if strings.HasSuffix(filename, ".js") { - name := prefix + share.SpecName(root, filename) - name = strings.ReplaceAll(name, ".", "/") - filename = filepath.Join(root, name+".page.json") - } + mod.Migrate(true) + fmt.Println(color.GreenString("[Watch] Model: %s Created", name)) + break - if op == "write" || op == "create" { - name := prefix + share.SpecName(root, filename) - content := share.ReadFile(filename) - page, err := page.LoadPage(content, name) // Relaod - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - - if page != nil { // Reload Script - dir := filepath.Dir(filename) - share.Walk(dir, ".js", func(root, filename string) { - script := share.ScriptName(filename) - content := share.ReadFile(filename) - _, err := page.LoadScriptReturn(string(content), script) - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - }) - } - - api, has := gou.APIs["xiang.page"] - if has { - _, err := gou.LoadAPIReturn(api.Source, api.Name) - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - } - fmt.Println(color.GreenString("Page %s Reloaded", name)) - - } else if op == "remove" || op == "rename" { - name := prefix + share.SpecName(root, filename) - if _, has := page.Pages[name]; has { - delete(page.Pages, name) - fmt.Println(color.RedString("Page %s Removed", name)) - } - } - - // 重启服务器 - if op == "write" || op == "create" || op == "remove" || op == "rename" { - StopWithouttSession(func() { - fmt.Println(color.GreenString("Service Restarted")) - go StartWithouttSession() - }) - } - }) -} - -// WatchWorkFlow 监听工作流更新 -func WatchWorkFlow(root string, prefix string) { - if share.DirNotExists(root) { - return - } - root = share.DirAbs(root) - go share.Watch(root, func(op string, filename string) { - if !strings.HasSuffix(filename, ".json") { + case "WRITE": + content, err := ioutil.ReadFile(file) + if err != nil { + fmt.Println(color.RedString("[Watch] Model: %s %s", name, err.Error())) return } - - if op == "write" || op == "create" { - name := prefix + share.SpecName(root, filename) - content := share.ReadFile(filename) - _, err := workflow.LoadWorkFlow(content, name) // Relaod - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - - api, has := gou.APIs["xiang.workflow."+name] - if has { - _, err := gou.LoadAPIReturn(api.Source, api.Name) - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - } - fmt.Println(color.GreenString("Workflow %s Reloaded", name)) - - } else if op == "remove" || op == "rename" { - name := prefix + share.SpecName(root, filename) - if _, has := workflow.WorkFlows[name]; has { - delete(workflow.WorkFlows, name) - fmt.Println(color.RedString("Workflow %s Removed", name)) - } + mod, err := gou.LoadModelReturn(string(content), name) + if err != nil { + fmt.Println(color.RedString("[Watch] Model: %s %s", name, err.Error())) + return } + mod.Migrate(false) + fmt.Println(color.GreenString("[Watch] Model: %s Reloaded", name)) + break - // 重启服务器 - if op == "write" || op == "create" || op == "remove" || op == "rename" { - StopWithouttSession(func() { - fmt.Println(color.GreenString("Service Restarted")) - go StartWithouttSession() - }) - } - }) + case "REMOVE", "RENAME": + delete(gou.Models, name) + fmt.Println(color.GreenString("[Watch] Model: %s Removed", name)) + break + } +} + +func watchReload(root string, file string, event string, cfg config.Config) { + + switch event { + case "CREATE", "WRITE", "REMOVE": + err := engine.Load(config.Conf) // 加载脚本等 + if err != nil { + fmt.Println(color.RedString("[Watch] Reload: %s", err.Error())) + } + + // Restart Server + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + StopWithContext(ctx, func() { + go Start() + fmt.Println(color.GreenString("[Watch] Reload Completed")) + }) + } } diff --git a/service/watch_test.go b/service/watch_test.go new file mode 100644 index 00000000..e1cd9b37 --- /dev/null +++ b/service/watch_test.go @@ -0,0 +1,153 @@ +package service + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" + "time" + + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/share" +) + +func TestWatch(t *testing.T) { + + share.DBConnect(config.Conf.DB) + err := Watch(config.Conf) + if err != nil { + t.Fatal(err) + } + defer StopWatch() + + createDir(t) + renameDir(t) + + createModel(t) + changeModel(t) + renameModel(t) + removeModel(t) + + createModel(t) + removeDir(t) +} + +func TestWatchReload(t *testing.T) { + go Start() + defer Stop(func() {}) + share.DBConnect(config.Conf.DB) + watchReload("", "", "", config.Conf) +} + +func createDir(t *testing.T) { + root := config.Conf.Root + file := filepath.Join(root, "models", "watch", "test") + fmt.Println("CREATE-DIR", file) + err := os.MkdirAll(file, os.ModePerm) + if err != nil { + t.Fatal(err) + } + time.Sleep(1 * time.Second) +} + +func renameDir(t *testing.T) { + root := config.Conf.Root + file := filepath.Join(root, "models", "watch", "test") + new := filepath.Join(root, "models", "watch", "test_new") + fmt.Println("RENAME-DIR", file) + err := os.Rename(file, new) + if err != nil { + t.Fatal(err) + } + time.Sleep(1 * time.Second) +} + +func removeDir(t *testing.T) { + root := config.Conf.Root + file := filepath.Join(root, "models", "watch") + fmt.Println("REMOVE-DIR", file) + err := os.RemoveAll(file) + if err != nil { + t.Fatal(err) + } + time.Sleep(1 * time.Second) +} + +func createModel(t *testing.T) { + dsl := ` + { + "name": "watch-test", + "table": { + "name": "watch_test", + "comment": "WatchTest", + "engine": "InnoDB" + }, + "columns": [ + { "name": "id", "type": "ID" }, + { "label": "Name", "name": "name", "type": "string", "index": true } + ], + "relations": {}, + "option": { "timestamps": true, "soft_deletes": true } + } + ` + root := config.Conf.Root + file := filepath.Join(root, "models", "watch", "test_new", "watch.mod.json") + fmt.Println("CREATE", file) + err := ioutil.WriteFile(file, []byte(dsl), 0644) + if err != nil { + t.Fatal(err) + } + time.Sleep(1 * time.Second) +} + +func changeModel(t *testing.T) { + dsl := ` + { + "name": "watch-test", + "table": { + "name": "watch_test", + "comment": "WatchTest", + "engine": "InnoDB" + }, + "columns": [ + { "name": "id", "type": "ID" }, + { "label": "Name", "name": "name", "type": "string", "index": true }, + { "label": "Data", "name": "data", "type": "json", "nullable": true } + ], + "relations": {}, + "option": { "timestamps": true, "soft_deletes": true } + } + ` + root := config.Conf.Root + file := filepath.Join(root, "models", "watch", "test_new", "watch.mod.json") + fmt.Println("CHANGE", file) + err := ioutil.WriteFile(file, []byte(dsl), 0644) + if err != nil { + t.Fatal(err) + } + time.Sleep(1 * time.Second) +} + +func renameModel(t *testing.T) { + root := config.Conf.Root + file := filepath.Join(root, "models", "watch", "test_new", "watch.mod.json") + new := filepath.Join(root, "models", "watch", "test_new", "watch_new.mod.json") + fmt.Println("RENAME", new) + err := os.Rename(file, new) + if err != nil { + t.Fatal(err) + } + time.Sleep(1 * time.Second) +} + +func removeModel(t *testing.T) { + root := config.Conf.Root + file := filepath.Join(root, "models", "watch", "test_new", "watch_new.mod.json") + fmt.Println("REMOVE", file) + err := os.Remove(file) + if err != nil { + t.Fatal(err) + } + time.Sleep(1 * time.Second) +} diff --git a/share/api_test.go b/share/api_test.go index 32f4083b..ed896949 100644 --- a/share/api_test.go +++ b/share/api_test.go @@ -9,8 +9,6 @@ import ( ) func TestGetQueryParam(t *testing.T) { - SessionServerStart() - defer SessionServerStop() sid := session.ID() s := session.Global().ID(sid).Expire(5000 * time.Microsecond) s.MustSet("id", 10086) diff --git a/share/const.go b/share/const.go index fa412a88..6788904c 100644 --- a/share/const.go +++ b/share/const.go @@ -1,7 +1,10 @@ package share // VERSION 版本号 -const VERSION = "0.9.1" +const VERSION = "0.10.1" + +// PRVERSION PreRelease Version +const PRVERSION = "DEV" // BUILDIN 打包应用合成一个制品 const BUILDIN = false diff --git a/share/session.go b/share/session.go index d47e3e18..3534ef0f 100644 --- a/share/session.go +++ b/share/session.go @@ -1,116 +1,77 @@ package share import ( - "context" "fmt" - "io" - "log" + "path/filepath" - klog "github.com/yaoapp/kun/log" - - "github.com/buraksezer/olric" - "github.com/buraksezer/olric/client" - config_olric "github.com/buraksezer/olric/config" - "github.com/buraksezer/olric/serializer" - "github.com/yaoapp/gou/session" - "github.com/yaoapp/kun/exception" + "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/config" - "github.com/yaoapp/yao/network" + + "github.com/yaoapp/gou/session" ) -var sessServer *olric.Olric +var sessionDB *session.BuntDB -// SessionPort Session 端口 -var SessionPort int - -func init() { - SessionPort = network.FreePort() - klog.Trace("session port: %d", SessionPort) +// SessionStart start session +func SessionStart() error { + if config.Conf.Session.Store == "file" { + return SessionFile() + } else if config.Conf.Session.Store == "redis" { + return SessionRedis() + } + return fmt.Errorf("Session Store config error %s (file|redis)", config.Conf.Session.Store) } -// SessionConnect 加载会话信息 -func SessionConnect(conf config.SessionConfig) { - - var clientConfig = &client.Config{ - Servers: []string{fmt.Sprintf("%s:%d", "127.0.0.1", SessionPort)}, - Serializer: serializer.NewMsgpackSerializer(), - Client: config_olric.NewClient(), - } - - c, err := client.New(clientConfig) - if err != nil { - exception.New("会话服务器连接失败 %s", 500, err.Error()).Throw() - } - - dm := c.NewDMap("local-session") - session.MemoryUse(session.ClientDMap{DMap: dm}) -} - -// SessionServerStop 关闭会话服务器 -func SessionServerStop() { - if sessServer != nil { - sessServer.Shutdown(context.Background()) +// SessionStop stop session +func SessionStop() { + if sessionDB != nil { + sessionDB.Close() } } -// SessionServerStart 启动会话服务器 -func SessionServerStart() { - - c := &config_olric.Config{ - BindAddr: "127.0.0.1", - BindPort: SessionPort, - ReadRepair: false, - ReplicaCount: 1, - WriteQuorum: 1, - ReadQuorum: 1, - MemberCountQuorum: 1, - Peers: []string{}, - DMaps: &config_olric.DMaps{}, - StorageEngines: config_olric.NewStorageEngine(), - Logger: &log.Logger{}, +// SessionRedis Connect redis server +func SessionRedis() error { + args := []string{} + if config.Conf.Session.Port == "" { + config.Conf.Session.Port = "6379" } - m, err := config_olric.NewMemberlistConfig("local") + if config.Conf.Session.DB == "" { + config.Conf.Session.DB = "1" + } + + args = append(args, config.Conf.Session.Port, config.Conf.Session.DB, config.Conf.Session.Password) + rdb, err := session.NewRedis(config.Conf.Session.Host, args...) if err != nil { - panic(fmt.Sprintf("unable to create a new memberlist config: %v", err)) - } - // m.BindAddr = config.Conf.Session.Host - m.BindPort = SessionPort - m.AdvertisePort = SessionPort - c.MemberlistConfig = m - - // c.MemberlistConfig.BindAddr = config.Conf.Session.Host - // c.MemberlistConfig.BindPort = 3308 - - // c := config_olric.New("local") - // c.BindAddr = config.Conf.Session.Host - // c.BindPort = config.Conf.Session.Port - - c.Logger.SetOutput(io.Discard) // 暂时关闭日志 - ctx, cancel := context.WithCancel(context.Background()) - c.Started = func() { - defer cancel() - klog.Trace("[INFO] Olric is ready to accept connections") + return err } - sessServer, err = olric.New(c) - - if err != nil { - klog.Error("Failed to create Olric instance: %v", err) - } - - go func() { - err = sessServer.Start() // Call Start at background. It's a blocker call. - if err != nil { - klog.Panic("olric.Start returned an error: %v", err) - } - }() - - <-ctx.Done() - dm, err := sessServer.NewDMap("local-session") - if err != nil { - klog.Panic("olric.NewDMap returned an error: %v", err) - } - - session.MemoryUse(session.ServerDMap{DMap: dm}) + session.Register("redis", rdb) + session.Name = "redis" + log.Trace("Session Store:REDIS HOST:%s PORT:%s DB:%s", config.Conf.Session.Host, config.Conf.Session.Port, config.Conf.Session.DB) + return nil +} + +// SessionFile Start session file +func SessionFile() error { + file := config.Conf.Session.File + if file == "" { + file = filepath.Join(config.Conf.Root, "data", ".session.db") + } + + file, err := filepath.Abs(file) + if err != nil { + return err + } + + burndb, err := session.NewBuntDB(file) + if err != nil { + return err + } + + session.Register("file", burndb) + session.Name = "file" + sessionDB = burndb + log.Trace("Session Store: File %s", file) + return nil } diff --git a/share/types.go b/share/types.go index 2f6253c9..705bf6d4 100644 --- a/share/types.go +++ b/share/types.go @@ -27,10 +27,11 @@ type ColumnImp Column // Column 字段呈现方式 type Column struct { - Label string `json:"label"` - View Render `json:"view,omitempty"` - Edit Render `json:"edit,omitempty"` - Form Render `json:"form,omitempty"` + Label string `json:"label"` + Export string `json:"export,omitempty"` + View Render `json:"view,omitempty"` + Edit Render `json:"edit,omitempty"` + Form Render `json:"form,omitempty"` Importable } @@ -71,8 +72,7 @@ type Page struct { // AppInfo 应用信息 type AppInfo struct { Name string `json:"name,omitempty"` - Lang string `json:"lang,omitempty"` - L map[string]string `json:"-"` // 应用的语言包 + L map[string]string `json:"-"` Short string `json:"short,omitempty"` Version string `json:"version,omitempty"` Description string `json:"description,omitempty"` diff --git a/share/watch.go b/share/watch.go index 41a13ac2..9d25802f 100644 --- a/share/watch.go +++ b/share/watch.go @@ -6,13 +6,14 @@ import ( "log" "os" "path/filepath" + "sync" "github.com/fatih/color" "github.com/fsnotify/fsnotify" "github.com/yaoapp/kun/exception" ) -var watchDone = []chan bool{} +var watchGroup sync.WaitGroup var watchOp = map[fsnotify.Op]string{ fsnotify.Create: "create", fsnotify.Write: "write", @@ -27,10 +28,12 @@ func Watch(root string, cb func(op string, file string)) { if err != nil { log.Fatal(err) } - defer watcher.Close() + defer func() { + watcher.Close() + watchGroup.Done() + }() - watchDone = append(watchDone, make(chan bool)) - last := len(watchDone) - 1 + watchGroup.Add(1) go func() { for { select { @@ -86,20 +89,6 @@ func Watch(root string, cb func(op string, file string)) { return nil }) - select { - case v := <-watchDone[last]: - fmt.Println(color.GreenString("Stop Watching: %s", root)) - if v == true { - break - } - } -} + watchGroup.Wait() -// StopWatch 停止监听 -func StopWatch() { - for i := range watchDone { - fmt.Println(color.GreenString("Stop: %d", i)) - watchDone[i] <- true - } - watchDone = []chan bool{} } diff --git a/share/watch_test.go b/share/watch_test.go index bc6e49e6..e05fff00 100644 --- a/share/watch_test.go +++ b/share/watch_test.go @@ -5,7 +5,6 @@ import ( "os" "path" "testing" - "time" "github.com/stretchr/testify/assert" ) @@ -16,7 +15,5 @@ func TestWatch(t *testing.T) { go Watch(root, func(op string, file string) { log.Println(op, file) }) - time.Sleep(time.Second * 2) - defer StopWatch() }) } diff --git a/server/socket.go b/socket/socket.go similarity index 77% rename from server/socket.go rename to socket/socket.go index 0ab88c5e..7a7e4723 100644 --- a/server/socket.go +++ b/socket/socket.go @@ -1,4 +1,4 @@ -package server +package socket import ( "fmt" @@ -11,9 +11,9 @@ import ( ) // Load 加载API -func Load(cfg config.Config) { - var root = filepath.Join(cfg.Root, "servers") - LoadFrom(root, "") +func Load(cfg config.Config) error { + var root = filepath.Join(cfg.Root, "sockets") + return LoadFrom(root, "") } // LoadFrom 从特定目录加载 @@ -26,7 +26,7 @@ func LoadFrom(dir string, prefix string) error { err := share.Walk(dir, ".sock.json", func(root, filename string) { name := prefix + share.SpecName(root, filename) content := share.ReadFile(filename) - _, err := gou.LoadServer(string(content), name) + _, err := gou.LoadSocket(string(content), name) if err != nil { log.With(log.F{"root": root, "file": filename}).Error(err.Error()) } diff --git a/server/socket_test.go b/socket/socket_test.go similarity index 78% rename from server/socket_test.go rename to socket/socket_test.go index 65f8d361..1f41a566 100644 --- a/server/socket_test.go +++ b/socket/socket_test.go @@ -1,4 +1,4 @@ -package server +package socket import ( "testing" @@ -16,8 +16,8 @@ func TestLoad(t *testing.T) { func check(t *testing.T) { keys := []string{} - for key := range gou.Servers { + for key := range gou.Sockets { keys = append(keys, key) } - assert.Equal(t, 1, len(keys)) + assert.Equal(t, 2, len(keys)) } diff --git a/store/store.go b/store/store.go new file mode 100644 index 00000000..4b5e3db6 --- /dev/null +++ b/store/store.go @@ -0,0 +1,36 @@ +package store + +import ( + "fmt" + "path/filepath" + + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/share" +) + +// Load load store +func Load(cfg config.Config) error { + var root = filepath.Join(cfg.Root, "stores") + return LoadFrom(root, "") +} + +// LoadFrom load from dir +func LoadFrom(dir string, prefix string) error { + + if share.DirNotExists(dir) { + return fmt.Errorf("%s does not exists", dir) + } + + err := share.Walk(dir, ".json", func(root, filename string) { + name := prefix + share.SpecName(root, filename) + content := share.ReadFile(filename) + _, err := gou.LoadStore(string(content), name) + if err != nil { + log.With(log.F{"root": root, "file": filename}).Error(err.Error()) + } + }) + + return err +} diff --git a/store/store_test.go b/store/store_test.go new file mode 100644 index 00000000..c569579f --- /dev/null +++ b/store/store_test.go @@ -0,0 +1,23 @@ +package store + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou/kv" + "github.com/yaoapp/yao/config" +) + +func TestLoad(t *testing.T) { + Load(config.Conf) + LoadFrom("not a path", "404.") + check(t) +} + +func check(t *testing.T) { + keys := []string{} + for key := range kv.Pools { + keys = append(keys, key) + } + assert.Equal(t, 1, len(keys)) +} diff --git a/system/process.go b/system/process.go new file mode 100644 index 00000000..555de4e5 --- /dev/null +++ b/system/process.go @@ -0,0 +1,42 @@ +package system + +import ( + "fmt" + "os/exec" + "strings" + + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/exception" +) + +func init() { + gou.RegisterProcessHandler("yao.system.Exec", processExec) +} + +// processExec execute the system command +func processExec(process *gou.Process) interface{} { + process.ValidateArgNums(1) + cmd := process.ArgsString(0) + + _, err := exec.LookPath(cmd) + if err != nil { + exception.New("command %s not found: %s", 400, cmd, err.Error()).Throw() + return nil + } + + args := []string{} + for i, arg := range process.Args { + if i == 0 { + continue + } + args = append(args, fmt.Sprintf("%v", arg)) + } + + res, err := exec.Command(cmd, args...).Output() + if err != nil { + exception.New("command %s error: %s", 500, cmd, err.Error()).Throw() + return nil + } + + return strings.TrimRight(string(res), "\n") +} diff --git a/system/process_test.go b/system/process_test.go new file mode 100644 index 00000000..054fd6d0 --- /dev/null +++ b/system/process_test.go @@ -0,0 +1,16 @@ +package system + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou" +) + +func TestProcessReturn(t *testing.T) { + output, err := gou.NewProcess("yao.system.Exec", "echo", "hello").Exec() + if err != nil { + t.Fatal(err) + } + assert.Equal(t, "hello", output) +} diff --git a/system/system.go b/system/system.go new file mode 100644 index 00000000..61e66e43 --- /dev/null +++ b/system/system.go @@ -0,0 +1,4 @@ +package system + +// Exec call system command +func Exec(cmd string, args ...string) {} diff --git a/table/lang.go b/table/lang.go new file mode 100644 index 00000000..4b23f071 --- /dev/null +++ b/table/lang.go @@ -0,0 +1,98 @@ +package table + +// Lang for applying a language pack +func (table *Table) Lang(trans func(widget string, inst string, value *string) bool) { + inst := table.Table + widget := "table" + + trans(widget, inst, &table.Name) + trans(widget, inst, &table.Decription) + + // Columns + for name, column := range table.Columns { + new := name + trans(widget, inst, &new) + trans(widget, inst, &column.Label) + + // Props + transMap(widget, inst, column.Edit.Props, trans) + transMap(widget, inst, column.View.Props, trans) + transMap(widget, inst, column.Form.Props, trans) + + delete(table.Columns, name) + table.Columns[new] = column + } + + // Filters + for name, filter := range table.Filters { + new := name + trans(widget, inst, &new) + trans(widget, inst, &filter.Label) + delete(table.Filters, name) + + // Props + transMap(widget, inst, filter.Input.Props, trans) + table.Filters[new] = filter + } + + // List + transMap(widget, inst, table.List.Layout, trans) + transMap(widget, inst, table.List.Option, trans) + + // Edit + transMap(widget, inst, table.Edit.Layout, trans) + transMap(widget, inst, table.Edit.Option, trans) +} + +func transMap(widget string, inst string, values map[string]interface{}, trans func(widget string, inst string, value *string) bool) { + for key, value := range values { + + switch value.(type) { + + case string: + v := value.(string) + trans(widget, inst, &v) + values[key] = v + break + + case []interface{}: + v := value.([]interface{}) + transArr(widget, inst, v, trans) + values[key] = v + break + + case map[string]interface{}: + v := value.(map[string]interface{}) + transMap(widget, inst, v, trans) + values[key] = v + break + } + + } +} + +func transArr(widget string, inst string, values []interface{}, trans func(widget string, inst string, value *string) bool) { + for key, value := range values { + switch value.(type) { + + case string: + v := value.(string) + trans(widget, inst, &v) + values[key] = v + break + + case []interface{}: + v := value.([]interface{}) + transArr(widget, inst, v, trans) + values[key] = v + break + + case map[string]interface{}: + v := value.(map[string]interface{}) + transMap(widget, inst, v, trans) + values[key] = v + break + } + + } +} diff --git a/table/process.go b/table/process.go index 82a071be..279a8a74 100644 --- a/table/process.go +++ b/table/process.go @@ -1,12 +1,19 @@ package table import ( + "crypto/md5" + "encoding/hex" + "fmt" + "os" + "path/filepath" "strings" + "time" "github.com/yaoapp/gou" "github.com/yaoapp/kun/any" "github.com/yaoapp/kun/log" "github.com/yaoapp/kun/maps" + "github.com/yaoapp/yao/xfs" ) func init() { @@ -22,6 +29,7 @@ func init() { gou.RegisterProcessHandler("xiang.table.QuickSave", ProcessQuickSave) gou.RegisterProcessHandler("xiang.table.UpdateIn", ProcessUpdateIn) gou.RegisterProcessHandler("xiang.table.DeleteIn", ProcessDeleteIn) + gou.RegisterProcessHandler("xiang.table.Export", ProcessExport) gou.RegisterProcessHandler("xiang.table.Setting", ProcessSetting) } @@ -35,11 +43,6 @@ func ProcessSearch(process *gou.Process) interface{} { table := Select(name) api := table.APIs["search"].ValidateLoop("xiang.table.search") - table.APIGuard(api.Guard, process.Sid, process.Global) - - // if process.NumOfArgsIs(5) && api.IsAllow(process.Args[4]) { - // return nil - // } // Before Hook process.Args = table.Before(table.Hooks.BeforeSearch, process.Args, process.Sid) @@ -52,7 +55,10 @@ func ProcessSearch(process *gou.Process) interface{} { pagesize := process.ArgsInt(3, api.DefaultInt(2)) // 查询数据 - response := gou.NewProcess(api.Process, param, page, pagesize).Run() + response := gou.NewProcess(api.Process, param, page, pagesize). + WithGlobal(process.Global). + WithSID(process.Sid). + Run() // After Hook return table.After(table.Hooks.AfterSearch, response, []interface{}{param, page, pagesize}, process.Sid) @@ -66,7 +72,6 @@ func ProcessFind(process *gou.Process) interface{} { name := process.ArgsString(0) table := Select(name) api := table.APIs["find"].ValidateLoop("xiang.table.find") - table.APIGuard(api.Guard, process.Sid, process.Global) // Before Hook process.Args = table.Before(table.Hooks.BeforeFind, process.Args, process.Sid) @@ -74,10 +79,17 @@ func ProcessFind(process *gou.Process) interface{} { // 参数表 process.ValidateArgNums(2) id := process.Args[1] - param := api.MergeDefaultQueryParam(gou.QueryParam{}, 1, process.Sid) + queryParam := gou.QueryParam{} + if process.NumOfArgs() == 3 { + queryParam = process.ArgsQueryParams(2) + } + param := api.MergeDefaultQueryParam(queryParam, 1, process.Sid) // 查询数据 - response := gou.NewProcess(api.Process, id, param).Run() + response := gou.NewProcess(api.Process, id, param). + WithGlobal(process.Global). + WithSID(process.Sid). + Run() // After Hook return table.After(table.Hooks.AfterFind, response, []interface{}{id, param}, process.Sid) @@ -92,7 +104,6 @@ func ProcessSave(process *gou.Process) interface{} { name := process.ArgsString(0) table := Select(name) api := table.APIs["save"].ValidateLoop("xiang.table.save") - table.APIGuard(api.Guard, process.Sid, process.Global) // Before Hook process.Args = table.Before(table.Hooks.BeforeSave, process.Args, process.Sid) @@ -101,7 +112,10 @@ func ProcessSave(process *gou.Process) interface{} { process.ValidateArgNums(2) // 查询数据 - response := gou.NewProcess(api.Process, process.Args[1]).Run() + response := gou.NewProcess(api.Process, process.Args[1]). + WithGlobal(process.Global). + WithSID(process.Sid). + Run() // After Hook return table.After(table.Hooks.AfterSave, response, []interface{}{process.Args[1]}, process.Sid) @@ -114,10 +128,18 @@ func ProcessDelete(process *gou.Process) interface{} { name := process.ArgsString(0) table := Select(name) api := table.APIs["delete"].ValidateLoop("xiang.table.delete") - table.APIGuard(api.Guard, process.Sid, process.Global) + + // Before Hook + process.Args = table.Before(table.Hooks.BeforeDelete, process.Args, process.Sid) id := process.Args[1] - return gou.NewProcess(api.Process, id).Run() + response := gou.NewProcess(api.Process, id). + WithGlobal(process.Global). + WithSID(process.Sid). + Run() + + // After Hook + return table.After(table.Hooks.AfterDelete, response, []interface{}{id}, process.Sid) } // ProcessDeleteWhere xiang.table.DeleteWhere @@ -128,7 +150,6 @@ func ProcessDeleteWhere(process *gou.Process) interface{} { name := process.ArgsString(0) table := Select(name) api := table.APIs["delete-where"].ValidateLoop("xiang.table.DeleteWhere") - table.APIGuard(api.Guard, process.Sid, process.Global) // 批量删除 param := api.MergeDefaultQueryParam(process.ArgsQueryParams(1), 0, process.Sid) @@ -136,7 +157,10 @@ func ProcessDeleteWhere(process *gou.Process) interface{} { param.Limit = 10 } - return gou.NewProcess(api.Process, param).Run() + return gou.NewProcess(api.Process, param). + WithGlobal(process.Global). + WithSID(process.Sid). + Run() } // ProcessDeleteIn xiang.table.DeleteIn @@ -147,7 +171,6 @@ func ProcessDeleteIn(process *gou.Process) interface{} { name := process.ArgsString(0) table := Select(name) api := table.APIs["delete-in"].ValidateLoop("xiang.table.DeleteIn") - table.APIGuard(api.Guard, process.Sid, process.Global) // 批量删除 ids := strings.Split(process.ArgsString(1), ",") @@ -158,7 +181,10 @@ func ProcessDeleteIn(process *gou.Process) interface{} { }, } - return gou.NewProcess(api.Process, param).Run() + return gou.NewProcess(api.Process, param). + WithGlobal(process.Global). + WithSID(process.Sid). + Run() } // ProcessUpdateWhere xiang.table.UpdateWhere @@ -169,14 +195,16 @@ func ProcessUpdateWhere(process *gou.Process) interface{} { name := process.ArgsString(0) table := Select(name) api := table.APIs["update-where"].ValidateLoop("xiang.table.UpdateWhere") - table.APIGuard(api.Guard, process.Sid, process.Global) // 批量更新 param := api.MergeDefaultQueryParam(process.ArgsQueryParams(1), 0, process.Sid) if param.Limit == 0 { // 限定删除行 param.Limit = 10 } - return gou.NewProcess(api.Process, param, process.Args[2]).Run() + return gou.NewProcess(api.Process, param, process.Args[2]). + WithGlobal(process.Global). + WithSID(process.Sid). + Run() } // ProcessUpdateIn xiang.table.UpdateWhere @@ -187,7 +215,6 @@ func ProcessUpdateIn(process *gou.Process) interface{} { name := process.ArgsString(0) table := Select(name) api := table.APIs["update-in"].ValidateLoop("xiang.table.UpdateIn") - table.APIGuard(api.Guard, process.Sid, process.Global) // 批量删除 ids := strings.Split(process.ArgsString(1), ",") @@ -197,7 +224,10 @@ func ProcessUpdateIn(process *gou.Process) interface{} { {Column: primary, OP: "in", Value: ids}, }, } - return gou.NewProcess(api.Process, param, process.Args[3]).Run() + return gou.NewProcess(api.Process, param, process.Args[3]). + WithGlobal(process.Global). + WithSID(process.Sid). + Run() } // ProcessInsert xiang.table.Insert @@ -207,8 +237,10 @@ func ProcessInsert(process *gou.Process) interface{} { name := process.ArgsString(0) table := Select(name) api := table.APIs["insert"].ValidateLoop("xiang.table.Insert") - table.APIGuard(api.Guard, process.Sid, process.Global) - return gou.NewProcess(api.Process, process.Args[1:]...).Run() + + return gou.NewProcess(api.Process, process.Args[1:]...). + WithGlobal(process.Global). + WithSID(process.Sid).Run() } // ProcessSetting xiang.table.Setting @@ -219,7 +251,6 @@ func ProcessSetting(process *gou.Process) interface{} { field := process.ArgsString(1) table := Select(name) api := table.APIs["setting"] - table.APIGuard(api.Guard, process.Sid, process.Global) fields := strings.Split(field, ",") if api.ProcessIs("xiang.table.Setting") { @@ -254,7 +285,9 @@ func ProcessSetting(process *gou.Process) interface{} { return setting } - return gou.NewProcess(api.Process, fields).Run() + return gou.NewProcess(api.Process, fields). + WithGlobal(process.Global). + WithSID(process.Sid).Run() } // ProcessQuickSave xiang.table.QuickSave @@ -264,7 +297,6 @@ func ProcessQuickSave(process *gou.Process) interface{} { name := process.ArgsString(0) table := Select(name) api := table.APIs["quicksave"].ValidateLoop("xiang.table.quicksave") - table.APIGuard(api.Guard, process.Sid, process.Global) args := []interface{}{} payload := process.ArgsMap(1) @@ -284,7 +316,9 @@ func ProcessQuickSave(process *gou.Process) interface{} { args = append(args, payload.Get("query")) } - return gou.NewProcess(api.Process, args...).Run() + return gou.NewProcess(api.Process, args...). + WithGlobal(process.Global). + WithSID(process.Sid).Run() } // ProcessSelect xiang.table.Select @@ -294,13 +328,80 @@ func ProcessSelect(process *gou.Process) interface{} { name := process.ArgsString(0) table := Select(name) api := table.APIs["select"].ValidateLoop("xiang.table.select") - table.APIGuard(api.Guard, process.Sid, process.Global) // Before Hook process.Args = table.Before(table.Hooks.BeforeSelect, process.Args, process.Sid) - response := gou.NewProcess(api.Process, process.Args[1:]...).Run() + response := gou.NewProcess(api.Process, process.Args[1:]...). + WithGlobal(process.Global). + WithSID(process.Sid).Run() // After Hook return table.After(table.Hooks.AfterSelect, response, process.Args[1:], process.Sid) } + +// ProcessExport xiang.table.Export (:table, :queryParam, :chunkSize) +// Export query result to Excel +func ProcessExport(process *gou.Process) interface{} { + + process.ValidateArgNums(1) + name := process.ArgsString(0) + table := Select(name) + api := table.APIs["search"].ValidateLoop("xiang.table.search") + + // Make DIR + hash := md5.Sum([]byte(time.Now().Format("20060102-15:04:05"))) + fingerprint := string(hex.EncodeToString(hash[:])) + fingerprint = strings.ToUpper(fingerprint) + dir := time.Now().Format("20060102") + ext := filepath.Ext("export.xlsx") + filename := filepath.Join(dir, fmt.Sprintf("%s%s", fingerprint, ext)) + xfs.Stor.MustMkdirAll(dir, os.ModePerm) + + page := 1 + for page > 0 { + + // Before Hook + process.Args = table.Before(table.Hooks.BeforeSearch, process.Args, process.Sid) + + // 参数表 + process.ValidateArgNums(3) + param := api.MergeDefaultQueryParam(process.ArgsQueryParams(1), 0, process.Sid) + pagesize := process.ArgsInt(2, api.DefaultInt(2)) + if process.NumOfArgs() == 4 { // for search hook + pagesize = process.ArgsInt(3, api.DefaultInt(1)) + } + + // 查询数据 + response := gou.NewProcess(api.Process, param, page, pagesize). + WithGlobal(process.Global). + WithSID(process.Sid). + Run() + + // After Hook + response = table.After(table.Hooks.AfterSearch, response, []interface{}{param, page, pagesize}, process.Sid) + + res, ok := response.(map[string]interface{}) + if !ok { + res, ok = response.(maps.MapStrAny) + if !ok { + page = -1 + continue + } + } + + if _, ok := res["next"]; !ok { + page = -1 + continue + } + + err := table.Export(filename, res["data"], page, pagesize) + if err != nil { + log.Error("Export %s %s", table.Table, err.Error()) + } + + page = any.Of(res["next"]).CInt() + } + + return filename +} diff --git a/table/process_test.go b/table/process_test.go index 7fddf946..c984fbb0 100644 --- a/table/process_test.go +++ b/table/process_test.go @@ -467,3 +467,44 @@ func TestTableProcessSelectWithHook(t *testing.T) { // 清空数据 capsule.Query().Table("service").WhereIn("id", []int{id}).Delete() } + +func TestTableProcessExport(t *testing.T) { + + args := []interface{}{ + "service", + gou.QueryParam{Wheres: []gou.QueryWhere{{Column: "status", Value: "enabled"}}}, + 2, + } + response := gou.NewProcess("xiang.table.Export", args...).Run() + assert.NotNil(t, response) + // fmt.Println(response) + // res := any.Of(response).Map() + // assert.True(t, res.Has("data")) + // assert.True(t, res.Has("next")) + // assert.True(t, res.Has("page")) + // assert.True(t, res.Has("pagecnt")) + // assert.True(t, res.Has("pagesize")) + // assert.True(t, res.Has("prev")) + // assert.True(t, res.Has("total")) + // assert.Equal(t, 1, res.Get("page")) + // assert.Equal(t, 2, res.Get("pagesize")) +} + +func TestTableProcessExportWithHook(t *testing.T) { + + args := []interface{}{"hooks.search"} + response := gou.NewProcess("xiang.table.Export", args...).Run() + + assert.NotNil(t, response) + // res := any.Of(response).Map() + // assert.True(t, res.Has("data")) + // assert.True(t, res.Has("next")) + // assert.True(t, res.Has("page")) + // assert.True(t, res.Has("pagecnt")) + // assert.True(t, res.Has("pagesize")) + // assert.True(t, res.Has("prev")) + // assert.True(t, res.Has("total")) + // assert.Equal(t, 1, res.Get("page")) + // assert.Equal(t, 2, res.Get("pagesize")) + // assert.Equal(t, float64(100), res.Get("after")) +} diff --git a/table/table.go b/table/table.go index b98347bb..39b24125 100644 --- a/table/table.go +++ b/table/table.go @@ -1,23 +1,117 @@ package table import ( + "errors" "fmt" "io" "os" "path/filepath" "strings" + "github.com/gin-gonic/gin" + "github.com/xuri/excelize/v2" "github.com/yaoapp/gou" "github.com/yaoapp/gou/helper" + "github.com/yaoapp/gou/lang" + "github.com/yaoapp/kun/any" "github.com/yaoapp/kun/exception" "github.com/yaoapp/kun/log" + "github.com/yaoapp/kun/maps" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/share" + "github.com/yaoapp/yao/xfs" ) // Tables 已载入模型 var Tables = map[string]*Table{} +// apiNames mapping +var apiNames = map[string]string{ + "/:name/search": "search", + "/:name/find/:id": "find", + "/:name/save": "save", + "/:name/delete/:id": "delete", + "/:name/insert": "insert", + "/:name/delete/in": "delete-in", + "/:name/delete/where": "delete-where", + "/:name/update/in": "update-in", + "/:name/update/where": "update-where", + "/:name/quicksave": "quicksave", + "/:name/select": "select", +} + +// Guard Table guard +func Guard(c *gin.Context) { + + if !strings.HasPrefix(c.FullPath(), "/api/xiang/table") { + c.Next() + return + } + + log.Trace("Table Guard FullPath: %s", c.FullPath()) + + routes := strings.Split(c.FullPath(), "/") + if len(routes) < 4 { + log.Trace("Table Guard Routes: %v", routes) + c.Next() + return + } + + path := "/" + strings.Join(routes[4:], "/") + apiName, has := apiNames[path] + if !has { + log.Trace("Table Guard API Name: %v", path) + c.Next() + return + } + + name, has := c.Params.Get("name") + if !has { + log.Trace("Table Guard Name: %v", c.Params) + c.Next() + return + } + + table, has := Tables[name] + if !has { + log.Trace("Table Guard Table: %s", name) + c.Next() + return + } + + api, has := table.APIs[apiName] + if !has { + log.Trace("Table Guard API: %s", apiName) + c.Next() + return + } + + log.Trace("Table Guard: %s %s %s %s", name, api.Guard, path, apiName) + + if api.Guard == "-" { + c.Next() + return + } + + if api.Guard == "" { + api.Guard = "bearer-jwt" + } + + guards := strings.Split(api.Guard, ",") + for _, guard := range guards { + guard = strings.TrimSpace(guard) + log.Trace("Table Guard: %s %s", name, guard) + if guard != "" { + if middleware, has := gou.HTTPGuards[guard]; has { + middleware(c) + continue + } + gou.ProcessGuard(guard)(c) + } + } + +} + // Load 加载数据表格 func Load(cfg config.Config) error { if share.BUILDIN { @@ -33,15 +127,21 @@ func LoadFrom(dir string, prefix string) error { return fmt.Errorf("%s does not exists", dir) } + messages := []string{} err := share.Walk(dir, ".json", func(root, filename string) { name := share.SpecName(root, filename) content := share.ReadFile(filename) _, err := LoadTable(string(content), name) if err != nil { log.With(log.F{"root": root, "file": filename}).Error(err.Error()) + messages = append(messages, fmt.Sprintf("%s %s", name, err.Error())) } }) + if len(messages) > 0 { + return fmt.Errorf("%s", strings.Join(messages, ";")) + } + return err } @@ -79,7 +179,20 @@ func LoadTable(source string, name string) (*Table, error) { table.loadColumns() table.loadFilters() table.loadAPIs() + + err = table.Validate() + if err != nil { + log.Error("[Table] %s is not valid: %s ", table.Table, err.Error()) + return nil, err + } + Tables[name] = &table + + // Apply a language pack + if lang.Default != nil { + lang.Default.Apply(Tables[name]) + } + return Tables[name], nil } @@ -125,9 +238,10 @@ func (table *Table) loadAPIs() { api.Process = table.APIs[name].Process } - // if table.APIs[name].Guard != "" { - // api.Guard = table.APIs[name].Guard - // } + api.Guard = "bearer-jwt" + if table.APIs[name].Guard != "" { + api.Guard = table.APIs[name].Guard + } if table.APIs[name].Default != nil { // fmt.Printf("\n%s.APIs[%s].Default: entry\n", table.Table, name) @@ -209,22 +323,6 @@ func (table *Table) After(process string, data interface{}, args []interface{}, return response } -// APIGuard API鉴权 -func (table *Table) APIGuard(guard string, sid string, global map[string]interface{}, args ...interface{}) { - if guard == "" { - guard = table.Guard - } - if guard == "-" || guard == "" { - return - } - - log.With(log.F{"args": args}).Debug(guard) - gou.NewProcess(guard, args...). - WithSID(sid). - WithGlobal(global). - Run() -} - // loadFilters 加载查询过滤器 func (table *Table) loadFilters() { if table.Bind.Model == "" { @@ -248,3 +346,128 @@ func (table *Table) loadColumns() { } table.Columns = defaults } + +// Export Export query result to Excel +func (table *Table) Export(filename string, data interface{}, page int, chunkSize int) error { + + rows := []maps.MapStr{} + if values, ok := data.([]maps.MapStrAny); ok { + for _, row := range values { + rows = append(rows, row.Dot()) + } + } else if values, ok := data.([]map[string]interface{}); ok { + for _, row := range values { + rows = append(rows, maps.Of(row).Dot()) + } + } else if values, ok := data.([]interface{}); ok { + for _, row := range values { + rows = append(rows, any.Of(row).MapStr().Dot()) + } + } + + columns, err := table.exportSetting() + if err != nil { + return err + } + + if len(columns) == 0 { + return fmt.Errorf("the table does not support export") + } + + filename = filepath.Join(xfs.Stor.Root, filename) + if _, err := os.Stat(filename); errors.Is(err, os.ErrNotExist) { + f := excelize.NewFile() + index := f.GetActiveSheetIndex() + name := f.GetSheetName(index) + f.SetSheetName(name, table.Name) + for i, column := range columns { + axis, err := excelize.CoordinatesToCellName(i+1, 1) + if err != nil { + return err + } + f.SetCellValue(table.Name, axis, column["name"]) + } + if err := f.SaveAs(filename); err != nil { + fmt.Println(err) + return err + } + } + + f, err := excelize.OpenFile(filename) + if err != nil { + return err + } + + defer f.Close() + offset := (page-1)*chunkSize + 2 + for line, row := range rows { + for i, column := range columns { + v := row.Get(column["field"]) + if v != nil { + axis, err := excelize.CoordinatesToCellName(i+1, line+offset) + if err != nil { + return err + } + f.SetCellValue(table.Name, axis, v) + } + } + // fmt.Println("--", line, page, offset, "--") + } + + return f.Save() +} + +func (table *Table) exportSetting() ([]map[string]string, error) { + // Validate params + if table.List.Layout == nil { + return nil, fmt.Errorf("the table layout does not found") + } + + columns, has := table.List.Layout["columns"] + if !has { + return nil, fmt.Errorf("the columns table layout does not found") + } + + fields, ok := columns.([]interface{}) + if !ok { + return nil, fmt.Errorf("Table Layout columns format error") + } + + setting := []map[string]string{} + for _, field := range fields { + f, ok := field.(map[string]interface{}) + if !ok { + continue + } + + n, has := f["name"] + if !has { + continue + } + + name, ok := n.(string) + if !ok { + continue + } + + column, has := table.Columns[name] + if !has { + continue + } + + field := column.Export + if field == "" { + if value, has := column.View.Props["value"]; has { + if valueStr, ok := value.(string); ok { + field = strings.TrimPrefix(valueStr, ":") + } + } + } + + if field != "" && name != "" { + setting = append(setting, map[string]string{"name": name, "field": field}) + } + } + + return setting, nil +} diff --git a/table/table_test.go b/table/table_test.go index c3e5f245..c57ec2a9 100644 --- a/table/table_test.go +++ b/table/table_test.go @@ -1,16 +1,22 @@ package table import ( + "os" "testing" "github.com/stretchr/testify/assert" "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/lang" "github.com/yaoapp/yao/model" "github.com/yaoapp/yao/share" ) func TestLoad(t *testing.T) { + + os.Setenv("YAO_LANG", "zh-hk") + lang.Load(config.Conf) + share.DBConnect(config.Conf.DB) share.Load(config.Conf) model.Load(config.Conf) @@ -48,5 +54,14 @@ func check(t *testing.T) { for key := range Tables { keys = append(keys, key) } - assert.Equal(t, 9, len(keys)) + assert.Equal(t, 10, len(keys)) + + demo := Select("demo") + assert.NotNil(t, demo.Columns["類型"]) + assert.NotNil(t, demo.Columns["類型"].Edit.Props["options"]) + + options := demo.Columns["類型"].Edit.Props["options"].([]interface{}) + opt1 := options[0].(map[string]interface{}) + assert.Equal(t, "貓", opt1["label"]) + } diff --git a/table/validate.go b/table/validate.go index 01df3881..6f5be474 100644 --- a/table/validate.go +++ b/table/validate.go @@ -1,6 +1,132 @@ package table +import "fmt" + // Validate 校验表格格式 -// func (table Table) Validate() error { -// return nil -// } +func (table Table) Validate() error { + err := table.validateList() + if err != nil { + return err + } + + err = table.validateEdit() + if err != nil { + return err + } + + return nil +} + +func (table Table) validateEdit() error { + if table.Edit.Layout == nil { + return nil + } + + fieldset, ok := table.Edit.Layout["fieldset"].([]interface{}) + if !ok { + return fmt.Errorf("edit.layout.columns is required") + } + + for idx, set := range fieldset { + set, ok := set.(map[string]interface{}) + if !ok { + return fmt.Errorf("edit.layout.fieldset.%d format error", idx) + } + + columns, ok := set["columns"].([]interface{}) + if !ok { + return fmt.Errorf("edit.layout.fieldset.%d.columns format error", idx) + } + + for cidx, column := range columns { + col, ismap := column.(map[string]interface{}) + name, isstr := column.(string) + if !ismap && !isstr { + return fmt.Errorf("edit.layout.fieldset.%d.columns.%d format error", idx, cidx) + } + + if ismap { + v, has := col["name"] + if !has { + return fmt.Errorf("edit.layout.fieldset.%d.columns.%d.name is required", idx, cidx) + } + namestr, ok := v.(string) + if !ok { + return fmt.Errorf("edit.layout.fieldset.%d.columns.%d.name format error", idx, cidx) + } + name = namestr + } + + if _, has := table.Columns[name]; !has { + return fmt.Errorf("edit.layout.fieldset.%d.columns.%d.name %s is not found in columns", idx, cidx, name) + } + } + + } + + return nil +} + +func (table Table) validateList() error { + + if table.List.Layout == nil { + return nil + } + + // table.List.Layout + columns, ok := table.List.Layout["columns"].([]interface{}) + if !ok { + return fmt.Errorf("list.layout.columns is required") + } + + for idx, column := range columns { + + col, ismap := column.(map[string]interface{}) + name, isstr := column.(string) + if !ismap && !isstr { + return fmt.Errorf("list.layout.columns.%d format error", idx) + } + + if ismap { + v, has := col["name"] + if !has { + return fmt.Errorf("list.layout.columns.%d.name is required", idx) + } + namestr, ok := v.(string) + if !ok { + return fmt.Errorf("list.layout.columns.%d.name format error", idx) + } + name = namestr + } + + if _, has := table.Columns[name]; !has { + return fmt.Errorf("list.layout.columns.%d.name %s is not found in columns", idx, name) + } + } + + filters, ok := table.List.Layout["filters"].([]interface{}) + if ok { + for idx, filter := range filters { + fli, ok := filter.(map[string]interface{}) + if !ok { + return fmt.Errorf("list.layout.filters.%d format error", idx) + } + + name, has := fli["name"] + if !has { + return fmt.Errorf("list.layout.filters.%d.name is required", idx) + } + + namestr, ok := name.(string) + if !ok { + return fmt.Errorf("list.layout.filters.%d.name format error", idx) + } + + if _, has := table.Filters[namestr]; !has { + return fmt.Errorf("list.layout.filters.%d.name %s is not found in filters", idx, namestr) + } + } + } + + return nil +} diff --git a/task/task.go b/task/task.go new file mode 100644 index 00000000..07d36e7e --- /dev/null +++ b/task/task.go @@ -0,0 +1,36 @@ +package task + +import ( + "fmt" + "path/filepath" + + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/share" +) + +// Load load task +func Load(cfg config.Config) error { + var root = filepath.Join(cfg.Root, "tasks") + return LoadFrom(root, "") +} + +// LoadFrom load from dir +func LoadFrom(dir string, prefix string) error { + + if share.DirNotExists(dir) { + return fmt.Errorf("%s does not exists", dir) + } + + err := share.Walk(dir, ".json", func(root, filename string) { + name := prefix + share.SpecName(root, filename) + content := share.ReadFile(filename) + _, err := gou.LoadTask(string(content), name) + if err != nil { + log.With(log.F{"root": root, "file": filename}).Error(err.Error()) + } + }) + + return err +} diff --git a/task/task_test.go b/task/task_test.go new file mode 100644 index 00000000..71f178a4 --- /dev/null +++ b/task/task_test.go @@ -0,0 +1,19 @@ +package task + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou/task" + "github.com/yaoapp/yao/config" +) + +func TestLoad(t *testing.T) { + Load(config.Conf) + LoadFrom("not a path", "404.") + check(t) +} + +func check(t *testing.T) { + assert.Equal(t, 1, len(task.Tasks)) +} diff --git a/tests/apis/chat.ws.json b/tests/apis/chat.ws.json new file mode 100644 index 00000000..eb94daed --- /dev/null +++ b/tests/apis/chat.ws.json @@ -0,0 +1,11 @@ +{ + "name": "A Chat WebSocket server", + "description": "A Chat WebSocket serverr", + "version": "0.9.2", + "protocols": ["yao-chat-01"], + "guard": "bearer-jwt", + "buffer": { "read": 1024, "write": 1024 }, + "limit": { "read-wait": 10, "pong-wait": 20, "max-message": 1024 }, + "timeout": 5, + "process": "flows.websocket.chat" +} diff --git a/tests/apis/demo.http.json b/tests/apis/demo.http.json new file mode 100644 index 00000000..b04f3239 --- /dev/null +++ b/tests/apis/demo.http.json @@ -0,0 +1,39 @@ +{ + "name": "Demo", + "version": "1.0.0", + "description": "demo", + "group": "demo", + "guard": "-", + "paths": [ + { + "path": "/user", + "method": "GET", + "process": "flows.demo.user", + "in": ["$query.id"], + "out": { + "status": 200, + "type": "application/json" + } + }, + { + "path": "/admin", + "method": "GET", + "process": "flows.demo.admin", + "in": ["$query.id"], + "out": { + "status": 200, + "type": "application/json" + } + }, + { + "path": "/token", + "method": "GET", + "process": "flows.demo.token", + "in": ["$query.id"], + "out": { + "status": 200, + "type": "application/json" + } + } + ] +} diff --git a/tests/certs/cert.pem b/tests/certs/cert.pem new file mode 100644 index 00000000..18ae9586 --- /dev/null +++ b/tests/certs/cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCjCCAfICCQDIMbr1hVnv2TANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQGEwJD +TjEQMA4GA1UECAwHQmVpamluZzEQMA4GA1UEBwwHSGFpZGlhbjEUMBIGA1UECgwL +WWFvQXBwcy5jb20wHhcNMjIwNzE2MDY0ODI5WhcNMzIwNzEzMDY0ODI5WjBHMQsw +CQYDVQQGEwJDTjEQMA4GA1UECAwHQmVpamluZzEQMA4GA1UEBwwHSGFpZGlhbjEU +MBIGA1UECgwLWWFvQXBwcy5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCqLwF385x1w0umsYpUb6LfSl/jUtny2T4hrVAAfzKcPDYyKT/gwJKJAm5v +PPLhsqjJLD27eRVNfzqCYOOJPIK5D+YAJigEb7/n+s96P7m0bzLZla3w/2GZRqZb +8m48xOQIHm/jVDC45Q3q/zdFUwgBtrOJxBBSs75eLRhj04CCPjdqoZcr0RdoyzSG +KvGiNGMNjfWEsdTnGYOcGNUa0UOsIpk+gS+QXD6EMlilM8GWQA+cDw6Owk5ydD0b +mPAiDD+mQn4wqCqruRuDziFe/cqWlY7etETT5Ox1D8eiLTTGRnj8p0TEro/eZ4Pc +JsVhH/t9yOvLgze3pgaPbbY6enTTAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAAp9 +7QuYnZF6YKp4zZp4VQkikAyYMvNnvQC9Fgah0RaX/ZY8hG7QQqiLDjcMPVaR13jT +oVumWJ225xO3HCi38AGHsJW8Uu7ZR4LZzI59MHenv10EIiTpc3HYAv+bH5bVtmgX +R7CEUMhNiJBKr6TwxiKBWkfjvO7sJrX4ZxKe6SKcOoKUeIMRyZCqstlSMlfu1xwr +DX83fyOyOaDJkUPIJNhVIbwqQxV0X47Bxdj9IdGF26ut4Dh0YNVxtArVr5va1VQo +Rsa9r63lhmnqbjeFcbY8TlO1Pm+hWgOmgcrjGNdP8DYi3KFMFiwY+VSUtwPnSZQS +OANAV1mlEv6+7UMZHRs= +-----END CERTIFICATE----- diff --git a/tests/certs/private.pem b/tests/certs/private.pem new file mode 100644 index 00000000..ae8df512 --- /dev/null +++ b/tests/certs/private.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCqLwF385x1w0um +sYpUb6LfSl/jUtny2T4hrVAAfzKcPDYyKT/gwJKJAm5vPPLhsqjJLD27eRVNfzqC +YOOJPIK5D+YAJigEb7/n+s96P7m0bzLZla3w/2GZRqZb8m48xOQIHm/jVDC45Q3q +/zdFUwgBtrOJxBBSs75eLRhj04CCPjdqoZcr0RdoyzSGKvGiNGMNjfWEsdTnGYOc +GNUa0UOsIpk+gS+QXD6EMlilM8GWQA+cDw6Owk5ydD0bmPAiDD+mQn4wqCqruRuD +ziFe/cqWlY7etETT5Ox1D8eiLTTGRnj8p0TEro/eZ4PcJsVhH/t9yOvLgze3pgaP +bbY6enTTAgMBAAECggEAGgj65ojkRLfN2QCMMwKpHhPYu4QvR5Goiapj9M+AxJoJ +iH77AL01LbSrhGUEmzajz7Lu9sE9Ww+9ubhs1s/lB77A5wDvWk5mQ20BVKhvFoV0 +eAkIBThsi/Bc/vAFoVJVuFA/ub1lfsO771l/8SEUtn5m1QqjnKNW2j133725iTMP +iwRlcEOEfFGa5bVQ/eEnQ5Fbk0OmIwfoSiFBTIsi2ZO7qTXaN5O1QzJKtSq0Jqbi +7cB0Z2cUi0QJ11jP4OcoawdVmx9InVwLkBfgR/AydmzClfnV7F8lk+m9UZmDV6/M +Wf+nAkijvYHvtmbb38qsHigE34agkvcy2YWoIstdcQKBgQDYi6fUsNu8RF2JC2NH +YWSnDS4JhYVP6pRugaH/RMe8+uX64JK1h85EXqf+rIWfmdkEN43p95lUxjObpEHE +pt2irrei8ARYqb7W0VkDfcEJnWoHMPlY/j7wunoIUFP1l8WWsrDyF3l+XnFLfRSj +rJ8/q/pPqJT2caYyOfF4EGltBQKBgQDJMOOVuoNAsZNttK9JwbORME+aK9OTXriT +VzHbqbMiyWBvqtnpRbwr6gHm+1nJgL4Fz1pT7vUxyXKzDIrRjx92qWp2sIFoYmAs +y0pdKChREbPAZUFIG5VJHqSgpStJq74Cq7Rrpmyg/rgKTNNCx7CwJ7Lap4OSr1tY +u5tZ/ARB9wKBgQCrXhn7X9sFKrU69yAiJRkaenusXVYa4HGG4l/pO+01yPiniYGQ +12CNCru395/npkDv7WcJeUCGrjO5QEwr7EIJtGrvLzLBV9QG/LWM5cAWc9SszD0I +CGyW5XwdiDRpbyqXD+KoM5uaPfM64kmrHbkJEniNAcShI1968BAADDziYQKBgHIG +UkXu0iqdOdgoWlwr+vBjeAoBosTYHzXqaih7sjoZ9Zz0MNKDolUlJ/TYKZgJbiju +ztvZB4AzEt2lUxvXyNZAyEUCXiVh8+4PPVM0yjzAEqcM/AW5FE+/nUAqvDDhtCvh +UrzcMp7AHmBAEE+9y0bbCQPhgrFkzxmoIjDKo0MbAoGAK58GTcH85twPkxdcskmv +wGzaUNXK4ggUvvXHbHDv0oVgx9aZR5dNclBkYcED0bQQL/c8aUk8hQkDj7TEvU6I +VDZ9iuJl2HRiVxRgl+cREqRhVhIoGbHCpZveymCIUjVWQqZtsWrtyI6R5gt+8LbW +eNJGBYZxsCZuBtBdhiFIlfI= +-----END PRIVATE KEY----- diff --git a/tests/certs/public.pem b/tests/certs/public.pem new file mode 100644 index 00000000..10edc968 --- /dev/null +++ b/tests/certs/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqi8Bd/OcdcNLprGKVG+i +30pf41LZ8tk+Ia1QAH8ynDw2Mik/4MCSiQJubzzy4bKoySw9u3kVTX86gmDjiTyC +uQ/mACYoBG+/5/rPej+5tG8y2ZWt8P9hmUamW/JuPMTkCB5v41QwuOUN6v83RVMI +AbazicQQUrO+Xi0YY9OAgj43aqGXK9EXaMs0hirxojRjDY31hLHU5xmDnBjVGtFD +rCKZPoEvkFw+hDJYpTPBlkAPnA8OjsJOcnQ9G5jwIgw/pkJ+MKgqq7kbg84hXv3K +lpWO3rRE0+TsdQ/Hoi00xkZ4/KdExK6P3meD3CbFYR/7fcjry4M3t6YGj222Onp0 +0wIDAQAB +-----END PUBLIC KEY----- diff --git a/tests/charts/lang.chart.json b/tests/charts/lang.chart.json new file mode 100644 index 00000000..f2553516 --- /dev/null +++ b/tests/charts/lang.chart.json @@ -0,0 +1,100 @@ +{ + "label": "::Demo", + "version": "1.0.0", + "description": "::Demo", + "nodes": [ + { + "name": "行业", + "engine": "xiang", + "query": { + "select": [ + "city", + ":COUNT(id) as 数量", + "industries[*](string 50) as industry" + ], + "from": "$service", + "wheres": [ + { "field": "created_at", ">": "?:$from.0" }, + { "field": "created_at", "<": "?:$to" } + ], + "orders": "数量 desc", + "limit": 100 + } + }, + { + "name": "计费", + "engine": "xiang", + "query": { + "select": ["city", ":COUNT(id) as 数量", "price_options[*] as option"], + "from": "$service", + "wheres": [ + { "field": "created_at", ">": "?:$from.0" }, + { "field": "created_at", "<": "?:$to" } + ], + "orders": "数量 desc", + "limit": 100 + } + }, + { + "name": "合并结果", + "process": "xiang.helper.ArrayPluck", + "args": [ + ["城市", "行业", "计费"], + { + "行业": { "key": "city", "value": "数量", "items": "{{$res.行业}}" }, + "计费": { "key": "city", "value": "数量", "items": "{{$res.计费}}" } + } + ] + } + ], + "output": { + "::Merge": "{{$res.合并结果}}", + "::Billing": "{{$res.计费}}", + "::Industry": "{{$res.行业}}", + "::Parameter": "{{$in}}" + }, + "apis": { + "data": { + "disable": false, + "guard": "-", + "default": [{ "from": "1980-01-02", "to": "2050-12-31" }] + }, + "setting": { "disable": false, "guard": "-" } + }, + "filters": { + "::Start": { + "label": "::Start", + "bind": "from", + "input": { + "type": "date", + "props": { + "placeholder": "::Please select a start time" + } + } + }, + "::End": { + "label": "::End", + "bind": "to", + "input": { + "type": "date", + "props": { + "placeholder": "::Please select an end time" + } + } + } + }, + "page": { + "primary": "::City", + "layout": { + "filters": [ + { "name": "::Start", "width": 6 }, + { "name": "::End", "width": 6 } + ], + "charts": [ + { "type": "line", "props": {} }, + { "type": "bar", "props": {} } + ] + }, + "actions": {} + } +} diff --git a/tests/charts/service/compare.chart.json b/tests/charts/service/compare.chart.json index 96557046..0c66d6fb 100644 --- a/tests/charts/service/compare.chart.json +++ b/tests/charts/service/compare.chart.json @@ -7,7 +7,7 @@ "name": "行业", "engine": "xiang", "query": { - "debug": true, + "debug": false, "select": [ "city", ":COUNT(id) as 数量", diff --git a/tests/charts/session.json b/tests/charts/session.json new file mode 100644 index 00000000..f806316d --- /dev/null +++ b/tests/charts/session.json @@ -0,0 +1,56 @@ +{ + "label": "指标对比", + "version": "1.0.0", + "description": "指标对比用于测试", + "nodes": [{ "name": "ID", "process": "session.Get", "args": ["id"] }], + "output": { + "ID": "{{$res.ID}}" + }, + "apis": { + "data": { + "disable": false, + "guard": "-", + "default": [{ "from": "1980-01-02", "to": "2050-12-31" }] + }, + "setting": { + "disable": false, + "guard": "-" + } + }, + "filters": { + "开始时间": { + "label": "开始时间", + "bind": "from", + "input": { + "type": "date", + "props": { + "placeholder": "请选择开始时间" + } + } + }, + "结束时间": { + "label": "结束时间", + "bind": "to", + "input": { + "type": "date", + "props": { + "placeholder": "请选择结束时间" + } + } + } + }, + "page": { + "primary": "城市", + "layout": { + "filters": [ + { "name": "开始时间", "width": 6 }, + { "name": "结束时间", "width": 6 } + ], + "charts": [ + { "type": "line", "props": {} }, + { "type": "bar", "props": {} } + ] + }, + "actions": {} + } +} diff --git a/tests/dyforms/app.form.json b/tests/dyforms/app.form.json new file mode 100644 index 00000000..4e2b288d --- /dev/null +++ b/tests/dyforms/app.form.json @@ -0,0 +1,58 @@ +{ + "guard": "bearer-jwt", + "title": "应用表单", + "sn": "AP1024", + "orign": "手机应用", + "columns": [ + { + "name": "门店", + "field": "store_id", + "searchable": true, + "props": { + "value": ":store_id", + "type": "SelectStore", + "title": "门店", + "placeholder": "请选择门店", + "validation": [ + { "type": "Required" }, + { "type": "StoreStatus", "args": [":store_id"] } + ] + } + }, + { + "name": "订单数量", + "field": "orders_amount", + "searchable": true, + "props": { + "value": ":orders_amount", + "type": "Number", + "title": "订单数量", + "placeholder": "今日订单数量", + "computed": "OrderAmount", + "validation": [ + { "type": "Required" }, + { "type": "IsNumber", "args": [":orders_amount"] } + ] + } + }, + { + "name": "账单地址", + "field": "address", + "props": { + "value": ":address", + "type": "Text", + "title": "账单地址", + "placeholder": "填写账单地址", + "validation": [ + { "type": "Required" }, + { "type": "MaxLength", "args": [":address", 200] } + ] + } + } + ], + "filters": [], + "list-layout": { + "columns": [{ "name": "store_id" }, { "name": "orders_amount" }], + "filters": [{ "name": "订单数量" }] + } +} diff --git a/tests/dyforms/website.form.json b/tests/dyforms/website.form.json new file mode 100644 index 00000000..4dc5be25 --- /dev/null +++ b/tests/dyforms/website.form.json @@ -0,0 +1,77 @@ +{ + "guard": "bearer-jwt", + "title": "官网表单", + "sn": "AP2048", + "orign": "官方网站", + "columns": [ + { + "name": "门店", + "field": "store_id", + "searchable": true, + "props": { + "value": ":store_id", + "type": "SelectStore", + "title": "门店", + "placeholder": "请选择门店", + "validation": [ + { "type": "Required" }, + { "type": "StoreStatus", "args": [":store_id"] } + ] + } + }, + { + "name": "订单数量", + "field": "orders_amount", + "searchable": true, + "props": { + "value": ":orders_amount", + "type": "Number", + "title": "订单数量", + "placeholder": "今日订单数量", + "computed": "OrderAmount", + "validation": [ + { "type": "Required" }, + { "type": "IsNumber", "args": [":orders_amount"] } + ] + } + }, + { + "name": "账单地址", + "field": "address", + "props": { + "value": ":address", + "type": "Text", + "title": "账单地址", + "placeholder": "填写账单地址", + "validation": [ + { "type": "Required" }, + { "type": "MaxLength", "args": [":address", 200] } + ] + } + } + ], + "filters": [ + { + "订单数量": { + "label": "订单数量超过", + "bind": "where.orders_amount.ge", + "type": "Select", + "props": { + "placeholder": "订单数量", + "options": [ + { "label": "大于100", "value": 100 }, + { "label": "大于50", "value": 50 } + ] + } + } + } + ], + "list-layout": { + "columns": [ + { "name": "store_id" }, + { "name": "orders_amount" }, + { "name": "address" } + ], + "filters": [{ "name": "订单数量" }] + } +} diff --git a/tests/flows/demo/admin.flow.json b/tests/flows/demo/admin.flow.json new file mode 100644 index 00000000..9fb64aa6 --- /dev/null +++ b/tests/flows/demo/admin.flow.json @@ -0,0 +1,61 @@ +{ + "label": "Auto Login", + "version": "1.0.0", + "description": "Auto Login for demo", + "nodes": [ + { + "name": "User", + "process": "models.xiang.user.Get", + "args": [ + { + "select": ["id", "email", "name", "type"], + "wheres": [{ "column": "id", "value": 1 }], + "limit": 1 + } + ] + }, + { + "name": "Menu", + "process": "flows.xiang.menu", + "args": ["{{$res.User.0.id}}"] + }, + { + "name": "SID", + "process": "session.start" + }, + { + "name": "JWT", + "process": "xiang.helper.JWTMake", + "args": [ + "{{$res.User.0.id}}", + {}, + { + "timeout": 3600, + "sid": "{{$res.SID}}" + } + ] + }, + { + "name": "Set User", + "process": "session.set", + "args": ["user", "{{$res.User.0}}"] + }, + { + "name": "Set issuer", + "process": "session.set", + "args": ["issuer", "xiang"] + }, + { + "name": "Set User ID", + "process": "session.set", + "args": ["user_id", "{{$res.User.0.id}}"] + } + ], + "output": { + "sid": "{{$res.SID}}", + "user": "{{$res.User.0}}", + "menus": "{{$res.Menu}}", + "token": "{{$res.JWT.token}}", + "expires_at": "{{$res.JWT.expires_at}}" + } +} diff --git a/tests/flows/demo/token.flow.json b/tests/flows/demo/token.flow.json new file mode 100644 index 00000000..ca9002b2 --- /dev/null +++ b/tests/flows/demo/token.flow.json @@ -0,0 +1,13 @@ +{ + "label": "Auto Login", + "version": "1.0.0", + "description": "Auto Login for demo", + "nodes": [ + { + "name": "User", + "process": "flows.demo.admin", + "args": [] + } + ], + "output": "{{$res.User.token}}" +} diff --git a/tests/flows/helper/hextostring.flow.json b/tests/flows/helper/hextostring.flow.json new file mode 100644 index 00000000..48fc2375 --- /dev/null +++ b/tests/flows/helper/hextostring.flow.json @@ -0,0 +1,13 @@ +{ + "label": "HexToString", + "version": "1.0.0", + "description": "HexToString", + "nodes": [ + { + "name": "Value", + "process": "xiang.helper.HexToString", + "args": ["ab"] + } + ], + "output": "{{$res.Value}}" +} diff --git a/tests/flows/websocket/chat.flow.json b/tests/flows/websocket/chat.flow.json new file mode 100644 index 00000000..e1dedde2 --- /dev/null +++ b/tests/flows/websocket/chat.flow.json @@ -0,0 +1,7 @@ +{ + "label": "a chat Websocket process", + "version": "1.0.0", + "description": "a chat Websocket process", + "nodes": [], + "output": "{{$in.0}}" +} diff --git a/tests/langs/en-US.json b/tests/langs/en-US.json deleted file mode 100644 index 54d43606..00000000 --- a/tests/langs/en-US.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "Yao": "Yao", - "象传应用引擎": "Yao App Engine Over load" -} diff --git a/tests/langs/zh-cn/flows/hello.flow.yml b/tests/langs/zh-cn/flows/hello.flow.yml new file mode 100644 index 00000000..e15a69fa --- /dev/null +++ b/tests/langs/zh-cn/flows/hello.flow.yml @@ -0,0 +1 @@ +Latest: 最新信息 diff --git a/tests/langs/zh-cn/global.yml b/tests/langs/zh-cn/global.yml new file mode 100644 index 00000000..61133bfe --- /dev/null +++ b/tests/langs/zh-cn/global.yml @@ -0,0 +1,7 @@ +Demo: 演示 +Phone: 电话号码 +ZipCode: 邮政编码 +Uptime: 更新时间 +Action: 动作 +Yao: YAO +Xiang: 象传 diff --git a/tests/langs/zh-cn/models/demo.mod.yml b/tests/langs/zh-cn/models/demo.mod.yml new file mode 100644 index 00000000..8eb7cc67 --- /dev/null +++ b/tests/langs/zh-cn/models/demo.mod.yml @@ -0,0 +1 @@ +SN: 编码 diff --git a/tests/langs/zh-cn/tables/demo.yml b/tests/langs/zh-cn/tables/demo.yml new file mode 100644 index 00000000..17bcd44b --- /dev/null +++ b/tests/langs/zh-cn/tables/demo.yml @@ -0,0 +1,10 @@ +ID: 序号 +SN: 编码 +Name: 姓名 +Kind: 类型 +Cat: 猫 +Dog: 狗 +Description: 介绍 +Keywords: 关键词 +Please input the SN: 请输入编码 +type the keywords...: 关键词... diff --git a/tests/langs/zh-hk/charts/lang.yml b/tests/langs/zh-hk/charts/lang.yml new file mode 100644 index 00000000..0f0a4714 --- /dev/null +++ b/tests/langs/zh-hk/charts/lang.yml @@ -0,0 +1,10 @@ +Start: 開始時間 +End: 結束時間 +City: 城市 +Please select a start time: 請選擇開始時間 +Please select an end time: 請選擇結束時間 +Industry: 行業 +Billing: 計費 +Merge Reulst: 合併結果 +Parameter: 參數 +Merge: 合併 diff --git a/tests/langs/zh-hk/flows/hello.flow.yml b/tests/langs/zh-hk/flows/hello.flow.yml new file mode 100644 index 00000000..e15a69fa --- /dev/null +++ b/tests/langs/zh-hk/flows/hello.flow.yml @@ -0,0 +1 @@ +Latest: 最新信息 diff --git a/tests/langs/zh-hk/global.yml b/tests/langs/zh-hk/global.yml new file mode 100644 index 00000000..c820c37e --- /dev/null +++ b/tests/langs/zh-hk/global.yml @@ -0,0 +1,7 @@ +Demo: 演示 +Phone: 電話號碼 +ZipCode: 郵政編碼 +Uptime: 更新時間 +Action: 動作 +Yao: YAO +Xiang: 像傳 diff --git a/tests/langs/zh-hk/models/demo.mod.yml b/tests/langs/zh-hk/models/demo.mod.yml new file mode 100644 index 00000000..30134633 --- /dev/null +++ b/tests/langs/zh-hk/models/demo.mod.yml @@ -0,0 +1 @@ +SN: 編碼 diff --git a/tests/langs/zh-hk/pages/lang.yml b/tests/langs/zh-hk/pages/lang.yml new file mode 100644 index 00000000..0f0a4714 --- /dev/null +++ b/tests/langs/zh-hk/pages/lang.yml @@ -0,0 +1,10 @@ +Start: 開始時間 +End: 結束時間 +City: 城市 +Please select a start time: 請選擇開始時間 +Please select an end time: 請選擇結束時間 +Industry: 行業 +Billing: 計費 +Merge Reulst: 合併結果 +Parameter: 參數 +Merge: 合併 diff --git a/tests/langs/zh-hk/tables/demo.yml b/tests/langs/zh-hk/tables/demo.yml new file mode 100644 index 00000000..42d52b75 --- /dev/null +++ b/tests/langs/zh-hk/tables/demo.yml @@ -0,0 +1,10 @@ +ID: 序號 +SN: 編碼 +Name: 姓名 +Kind: 類型 +Description: 介绍 +Keywords: 關鍵詞 +Cat: 貓 +Dog: 狗 +Please input the SN: 請輸入編碼 +type the keywords...: 關鍵詞... diff --git a/tests/models/demo.mod.json b/tests/models/demo.mod.json new file mode 100644 index 00000000..e827cdcd --- /dev/null +++ b/tests/models/demo.mod.json @@ -0,0 +1,35 @@ +{ + "name": "::Demo", + "connector": "trace", + "table": { "name": "user_trace", "comment": "::Demo" }, + "columns": [ + { "label": "ID", "name": "id", "type": "ID" }, + { + "label": "::SN", + "name": "user_sn", + "type": "string", + "length": 42, + "index": true + }, + { + "label": "::Uptime", + "name": "uptime", + "type": "datetime", + "index": true + }, + { + "label": "::Action", + "name": "action", + "type": "enum", + "option": ["进入", "离开"], + "index": true + } + ], + "indexes": [ + { + "name": "user_sn_action_unique", + "type": "unique", + "columns": ["user_sn", "action"] + } + ] +} diff --git a/tests/pages/lang.page.json b/tests/pages/lang.page.json new file mode 100644 index 00000000..f2553516 --- /dev/null +++ b/tests/pages/lang.page.json @@ -0,0 +1,100 @@ +{ + "label": "::Demo", + "version": "1.0.0", + "description": "::Demo", + "nodes": [ + { + "name": "行业", + "engine": "xiang", + "query": { + "select": [ + "city", + ":COUNT(id) as 数量", + "industries[*](string 50) as industry" + ], + "from": "$service", + "wheres": [ + { "field": "created_at", ">": "?:$from.0" }, + { "field": "created_at", "<": "?:$to" } + ], + "orders": "数量 desc", + "limit": 100 + } + }, + { + "name": "计费", + "engine": "xiang", + "query": { + "select": ["city", ":COUNT(id) as 数量", "price_options[*] as option"], + "from": "$service", + "wheres": [ + { "field": "created_at", ">": "?:$from.0" }, + { "field": "created_at", "<": "?:$to" } + ], + "orders": "数量 desc", + "limit": 100 + } + }, + { + "name": "合并结果", + "process": "xiang.helper.ArrayPluck", + "args": [ + ["城市", "行业", "计费"], + { + "行业": { "key": "city", "value": "数量", "items": "{{$res.行业}}" }, + "计费": { "key": "city", "value": "数量", "items": "{{$res.计费}}" } + } + ] + } + ], + "output": { + "::Merge": "{{$res.合并结果}}", + "::Billing": "{{$res.计费}}", + "::Industry": "{{$res.行业}}", + "::Parameter": "{{$in}}" + }, + "apis": { + "data": { + "disable": false, + "guard": "-", + "default": [{ "from": "1980-01-02", "to": "2050-12-31" }] + }, + "setting": { "disable": false, "guard": "-" } + }, + "filters": { + "::Start": { + "label": "::Start", + "bind": "from", + "input": { + "type": "date", + "props": { + "placeholder": "::Please select a start time" + } + } + }, + "::End": { + "label": "::End", + "bind": "to", + "input": { + "type": "date", + "props": { + "placeholder": "::Please select an end time" + } + } + } + }, + "page": { + "primary": "::City", + "layout": { + "filters": [ + { "name": "::Start", "width": 6 }, + { "name": "::End", "width": 6 } + ], + "charts": [ + { "type": "line", "props": {} }, + { "type": "bar", "props": {} } + ] + }, + "actions": {} + } +} diff --git a/tests/pages/service/compare.page.json b/tests/pages/service/compare.page.json index 52a284a9..5478c7c6 100644 --- a/tests/pages/service/compare.page.json +++ b/tests/pages/service/compare.page.json @@ -7,7 +7,7 @@ "name": "行业", "engine": "xiang", "query": { - "debug": true, + "debug": false, "select": [ "city", ":COUNT(id) as 数量", diff --git a/tests/schedules/mail.sch.json b/tests/schedules/mail.sch.json new file mode 100644 index 00000000..fc9c1084 --- /dev/null +++ b/tests/schedules/mail.sch.json @@ -0,0 +1,6 @@ +{ + "name": "每分钟发送一封邮件", + "schedule": "*/1 * * * *", + "process": "scripts.mail.Send", + "args": [null, "$ENV.SEND_MAIL_TEST_MAIL"] +} diff --git a/tests/schedules/sendmail.sch.json b/tests/schedules/sendmail.sch.json new file mode 100644 index 00000000..b6d6e0bc --- /dev/null +++ b/tests/schedules/sendmail.sch.json @@ -0,0 +1,6 @@ +{ + "name": "每分钟发送一封邮件", + "schedule": "*/1 * * * *", + "task": "mail", + "args": ["$ENV.SEND_MAIL_TEST_MAIL"] +} diff --git a/tests/scripts/helper.js b/tests/scripts/helper.js new file mode 100644 index 00000000..05b95d1f --- /dev/null +++ b/tests/scripts/helper.js @@ -0,0 +1,18 @@ +function HexToStringString() { + return Process("xiang.helper.HexToString", "ab"); +} + +function HexToStringBytes(data) { + return Process("xiang.helper.HexToString", data); +} + +function Buffer() { + var buffer = new Uint8Array(2); + buffer[0] = 0x0; + buffer[1] = 0x1; + return BufferToString(buffer); +} + +function BufferToString(buffer) { + return String.fromCharCode.apply(null, new Uint8Array(buffer)); +} diff --git a/tests/scripts/mail.js b/tests/scripts/mail.js new file mode 100644 index 00000000..cc8a4fb4 --- /dev/null +++ b/tests/scripts/mail.js @@ -0,0 +1,53 @@ +var id = 1024; + +/** + * Generate job id + * @returns + */ +function NextID() { + id = id + 1; + console.log(`NextID: ${id}`); + return id; +} + +function Send(id, mail, flag) { + for (var i = 1; i <= 3; i++) { + Process("xiang.sys.Sleep", 200); + Process("tasks.mail.Progress", id, i, 3, "unit-test"); + } + if (flag) { + console.log(`flag: ${flag}`); + Process("xiang.sys.Sleep", 2000); + } + console.log( + `Send: ${JSON.stringify({ foo: "bar", mail: mail, flag: flag || "-" })}` + ); + return { foo: "bar", mail: mail, flag: flag || "-" }; +} + +/** + * OnAdd add event + * @param {*} id + */ +function OnAdd(id) { + console.log(`OnAdd: #${id}`); +} + +/** + * OnProgress + * @param {*} id + * @param {*} current + * @param {*} total + * @param {*} message + */ +function OnProgress(id, current, total, message) { + console.log(`OnProgress: #${id} ${message} ${current}/${total} `); +} + +function OnSuccess(id, res) { + console.log(`OnSuccess: #${id} ${JSON.stringify(res)}`); +} + +function OnError(id, err) { + console.log(`OnError: #${id} ${err}`); +} diff --git a/tests/scripts/socket.js b/tests/scripts/socket.js new file mode 100644 index 00000000..c22fc669 --- /dev/null +++ b/tests/scripts/socket.js @@ -0,0 +1,34 @@ +function onData(data, recvLen) { + console.log(`Data: ${data} ${recvLen}`); + log.Trace("onData: %v %v", data, recvLen); +} + +function onError(err) { + console.log(`Error: ${err} `); +} + +function onClosed(data, err) { + console.log(`Closed: ${data} ${err} `); +} + +function onConnected(option) { + console.log("onConnected", option); +} + +// Convert a hex string to a byte array +function hexToBytes(hex) { + for (var bytes = [], c = 0; c < hex.length; c += 2) { + bytes.push(parseInt(hex.substr(c, 2), 16)); + } + return bytes; +} + +// Convert a byte array to a hex string +function bytesToHex(bytes) { + for (var hex = [], i = 0; i < bytes.length; i++) { + var current = bytes[i] < 0 ? bytes[i] + 256 : bytes[i]; + hex.push((current >>> 4).toString(16)); + hex.push((current & 0xf).toString(16)); + } + return hex.join(""); +} diff --git a/tests/scripts/websocket.js b/tests/scripts/websocket.js new file mode 100644 index 00000000..dd5182ba --- /dev/null +++ b/tests/scripts/websocket.js @@ -0,0 +1,91 @@ +function Hello() { + var ws = new WebSocket("ws://127.0.0.1:5093/websocket/chat", "p0"); + var response = ws.push("Hello World"); + return response; +} + +const host = "127.0.0.1:5099"; + +/** + * WebSocket Client EventMode(daemon) + */ +function Event() { + var url = `ws://${host}/websocket/chat`; + var ws = new WebSocket(url, "yao-chat-01"); + + // Connection opened + ws.on("open", function (event) { + ws.send("Hello Server!", event.data); + }); + + // Listen for messages + ws.on("message", function (event) { + console.log("Message from server ", event.data); + ws.close(200, "Bye"); + }); + + // Listen for error + ws.on("error", (event) => { + console.log("Message from server ", event); + }); + + // Listen for close + ws.on("close", (event) => { + console.log("The connection has been closed successfully"); + }); +} + +/** + * WebSocket Client PushMode + */ +function Push() { + var url = `ws://${host}/websocket/chat`; + var ws = new WebSocket(url, "yao-chat-01"); + var message = ws.push("Hello Server!"); + console.log(message); +} + +/** + * WebSocket Client JWT Auth + */ +function Token() { + var token = "xxx"; + var url = `ws://${host}/websocket/chat?token=${token}`; + var ws = new WebSocket(url, "yao-chat-01"); + var message = ws.push("Hello Server!"); + console.log(message); +} + +/** + * WebSocket Client Basic Auth + */ +function Basic() { + // Basic Auth username:password + var user = "test"; + var password = "WsTest123**"; + var url = `ws://${user}:${password}@${host}/websocket/chat?token=${token}`; + var ws = new WebSocket(url, "yao-chat-01"); + var message = ws.push("Hello Server!"); + console.log(message); +} + +function onData(data, recvLen) { + console.log(`Data: ${data} ${recvLen}`); + log.Trace("onData: %v %v", data, recvLen); + if (data[0] == "1") { + Process("websocket.Close", "message"); + } +} + +function onError(err) { + console.log(`Error: ${err} `); +} + +function onClosed(data, err) { + console.log(`Closed: ${data} ${err} `); +} + +function onConnected(option) { + console.log("onConnected", option); + Process("websocket.Write", "message", "1|Hello World"); +} diff --git a/tests/servers/rfid.sock.json b/tests/servers/rfid.sock.json deleted file mode 100644 index 7f908a77..00000000 --- a/tests/servers/rfid.sock.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "RFID接收器", - "description": "用来接收并处理RIFD", - "version": "0.0.1", - "protocol": "tcp", - "host": "0.0.0.0", - "port": "3019", - "process": "flows.rfid.read" -} diff --git a/tests/services/cmd.srv.json b/tests/services/cmd.srv.json new file mode 100644 index 00000000..844a2386 --- /dev/null +++ b/tests/services/cmd.srv.json @@ -0,0 +1,15 @@ +{ + "name": "Service for receiving RFID", + "description": "Service for receiving RFID", + "version": "0.9.2", + "restart": "on-failure", + "requires": ["syslog"], + "after": ["syslog"], + "error": "/var/log/test.err", + "output": "/var/log/test.log", + "workdir": "~", + "command": "tail", + "args": ["-f", "/dev/null"], + "user": "root", + "group": "root" +} diff --git a/tests/services/process.srv.json b/tests/services/process.srv.json new file mode 100644 index 00000000..5c32b997 --- /dev/null +++ b/tests/services/process.srv.json @@ -0,0 +1,15 @@ +{ + "name": "Service for receiving RFID", + "description": "Service for receiving RFID", + "version": "0.9.2", + "restart": "on-failure", + "requires": ["syslog"], + "after": ["syslog"], + "error": "/var/log/test.err", + "output": "/var/log/test.log", + "workdir": "~", + "process": "xiang.sys.ping", + "args": [], + "user": "root", + "group": "root" +} diff --git a/tests/sockets/rfid.sock.json b/tests/sockets/rfid.sock.json new file mode 100644 index 00000000..1da22f0a --- /dev/null +++ b/tests/sockets/rfid.sock.json @@ -0,0 +1,17 @@ +{ + "name": "RFID Receiver (Server Mode)", + "description": "RFID Receiver", + "mode": "server", + "version": "0.0.1", + "protocol": "tcp", + "host": "0.0.0.0", + "port": "3019", + "event": { + "data": "scripts.socket.onData", + "closed": "scripts.socket.onClosed", + "connected": "scripts.socket.onConnected", + "error": "scripts.socket.onError" + }, + "keep": 0, + "buffer": 60 +} diff --git a/tests/sockets/rfid_client.sock.json b/tests/sockets/rfid_client.sock.json new file mode 100644 index 00000000..a66fdda7 --- /dev/null +++ b/tests/sockets/rfid_client.sock.json @@ -0,0 +1,19 @@ +{ + "name": "RFID Receiver (Client Mode)", + "description": "RFID Receiver", + "mode": "client", + "version": "0.9.2", + "protocol": "tcp", + "host": "192.168.31.33", + "port": "3019", + "event": { + "data": "scripts.socket.onData", + "closed": "scripts.socket.onClosed", + "connected": "scripts.socket.onConnected", + "error": "scripts.socket.onError" + }, + "buffer": 60, + "keep": 0, + "attempts": 9, + "attempt_after": 10 +} diff --git a/tests/stores/share.lru.json b/tests/stores/share.lru.json new file mode 100644 index 00000000..7ecbd3a7 --- /dev/null +++ b/tests/stores/share.lru.json @@ -0,0 +1,6 @@ +{ + "name": "Share LRU Cache", + "description": "The share memory LRU cache", + "type": "lru", + "option": { "size": 1024 } +} diff --git a/tests/tables/assign.tab.json b/tests/tables/assign.tab.json index db60aa71..093e85f6 100644 --- a/tests/tables/assign.tab.json +++ b/tests/tables/assign.tab.json @@ -8,7 +8,7 @@ "layout": { "fieldset": [ { - "columns": [{ "name": "商务负责人", "width": 24 }] + "columns": [{ "name": "name", "width": 24 }] } ] } diff --git a/tests/tables/demo.tab.json b/tests/tables/demo.tab.json new file mode 100644 index 00000000..11d38e06 --- /dev/null +++ b/tests/tables/demo.tab.json @@ -0,0 +1,85 @@ +{ + "name": "::Demo", + "version": "1.0.0", + "decription": "::Demo", + "bind": { "model": "demo" }, + "apis": {}, + "columns": { + "::ID": { + "label": "::ID", + "view": { "type": "label", "props": { "value": ":id" } } + }, + "::SN": { + "label": "::SN", + "view": { "type": "label", "props": { "value": ":sn" } }, + "edit": { + "type": "input", + "props": { "value": ":sn", "placeholder": "::Please input the SN" } + } + }, + "::Name": { + "label": "::Name", + "view": { "type": "label", "props": { "value": ":name" } }, + "edit": { "type": "input", "props": { "value": ":name" } } + }, + "::Kind": { + "label": "::Kind", + "view": { "type": "label", "props": { "value": ":kind" } }, + "edit": { + "type": "select", + "props": { + "value": ":kind", + "options": [ + { "label": "::Cat", "value": "cat" }, + { "label": "::Dog", "value": "dog" } + ] + } + } + }, + "::Description": { + "label": "::Description", + "view": { "type": "label", "props": { "value": ":desc" } }, + "edit": { "type": "textArea", "props": { "value": ":desc", "rows": 4 } } + } + }, + "filters": { + "::Keywords": { + "label": "::Keywords", + "bind": "where.name.match", + "input": { + "type": "input", + "props": { "placeholder": "::type the keywords..." } + } + } + }, + "list": { + "primary": "id", + "layout": { + "columns": [ + { "name": "::ID", "width": 80 }, + { "name": "::SN", "width": 100 }, + { "name": "::Name", "width": 200 }, + { "name": "::Kind" } + ], + "filters": [{ "name": "::Keywords" }] + }, + "actions": { "pagination": { "props": { "showTotal": true } } }, + "option": { "operation": { "unfold": true } } + }, + "edit": { + "primary": "id", + "layout": { + "fieldset": [ + { + "columns": [ + { "name": "::SN", "width": 8 }, + { "name": "::Name", "width": 8 }, + { "name": "::Kind", "width": 8 }, + { "name": "::Description", "width": 24 } + ] + } + ] + }, + "actions": { "cancel": {}, "save": {}, "delete": {} } + } +} diff --git a/tests/tables/foo/bar.tab.json b/tests/tables/foo/bar.tab.json index 0cd473dc..fb76ec06 100644 --- a/tests/tables/foo/bar.tab.json +++ b/tests/tables/foo/bar.tab.json @@ -11,47 +11,13 @@ "list": { "primary": "id", "layout": { - "columns": [ - { - "name": "名称", - "width": "240px" - }, - { - "name": "父节点" - }, - { - "name": "图标" - }, - { - "name": "状态" - }, - { - "name": "路由" - }, - { - "name": "排列" - }, - { - "name": "block布局" - }, - { - "name": "显示" - } - ], - "filters": [ - { - "name": "名称", - "width": 6 - } - ] + "columns": [{ "name": "name", "width": "240px" }], + "filters": [{ "name": "name", "width": 6 }] }, "actions": { "create": { "type": "button", - "props": { - "label": "新建菜单", - "icon": "fas fa-plus" - } + "props": { "label": "新建菜单", "icon": "fas fa-plus" } }, "view": {}, "edit": {}, @@ -75,30 +41,8 @@ "title": "基础信息", "description": "", "columns": [ - { - "name": "名称", - "width": 6 - }, - { - "name": "图标", - "width": 6 - }, - { - "name": "路由", - "width": 6 - }, - { - "name": "排列", - "width": 6 - }, - { - "name": "block布局", - "width": 6 - }, - { - "name": "显示", - "width": 6 - } + { "name": "name", "width": 6 }, + { "name": "id", "width": 6 } ] } ] diff --git a/tests/tables/hooks/find.tab.json b/tests/tables/hooks/find.tab.json index b0016c95..eb5c1233 100644 --- a/tests/tables/hooks/find.tab.json +++ b/tests/tables/hooks/find.tab.json @@ -13,7 +13,7 @@ "list": { "primary": "id", "layout": { - "columns": [{ "name": "ID", "width": 6 }], + "columns": [{ "name": "id", "width": 6 }], "filters": [] }, "actions": {} @@ -21,7 +21,7 @@ "edit": { "primary": "id", "layout": { - "fieldset": [{ "columns": [{ "name": "ID", "width": 6 }] }] + "fieldset": [{ "columns": [{ "name": "id", "width": 6 }] }] }, "actions": { "cancel": {} } }, diff --git a/tests/tables/hooks/save.tab.json b/tests/tables/hooks/save.tab.json index d0e1c859..455093aa 100644 --- a/tests/tables/hooks/save.tab.json +++ b/tests/tables/hooks/save.tab.json @@ -13,7 +13,7 @@ "list": { "primary": "id", "layout": { - "columns": [{ "name": "ID", "width": 6 }], + "columns": [{ "name": "id", "width": 6 }], "filters": [] }, "actions": {} @@ -21,7 +21,7 @@ "edit": { "primary": "id", "layout": { - "fieldset": [{ "columns": [{ "name": "ID", "width": 6 }] }] + "fieldset": [{ "columns": [{ "name": "id", "width": 6 }] }] }, "actions": { "cancel": {} } }, diff --git a/tests/tables/hooks/search.tab.json b/tests/tables/hooks/search.tab.json index d38f27b5..926d544f 100644 --- a/tests/tables/hooks/search.tab.json +++ b/tests/tables/hooks/search.tab.json @@ -22,7 +22,7 @@ "list": { "primary": "id", "layout": { - "columns": [{ "name": "ID", "width": 6 }], + "columns": [{ "name": "id", "width": 6 }, { "name": "城市" }], "filters": [] }, "actions": {} @@ -30,7 +30,7 @@ "edit": { "primary": "id", "layout": { - "fieldset": [{ "columns": [{ "name": "ID", "width": 6 }] }] + "fieldset": [{ "columns": [{ "name": "id", "width": 6 }] }] }, "actions": { "cancel": {} } }, diff --git a/tests/tables/hooks/select.tab.json b/tests/tables/hooks/select.tab.json index ea4e031f..1c9f6dc1 100644 --- a/tests/tables/hooks/select.tab.json +++ b/tests/tables/hooks/select.tab.json @@ -13,7 +13,7 @@ "list": { "primary": "id", "layout": { - "columns": [{ "name": "ID", "width": 6 }], + "columns": [{ "name": "id", "width": 6 }], "filters": [] }, "actions": {} @@ -21,7 +21,7 @@ "edit": { "primary": "id", "layout": { - "fieldset": [{ "columns": [{ "name": "ID", "width": 6 }] }] + "fieldset": [{ "columns": [{ "name": "id", "width": 6 }] }] }, "actions": { "cancel": {} } }, diff --git a/tests/tables/service.json b/tests/tables/service.json index 871bc495..641d9a6b 100644 --- a/tests/tables/service.json +++ b/tests/tables/service.json @@ -316,11 +316,13 @@ "primary": "id", "layout": { "columns": [ + { "name": "id", "width": 6 }, { "name": "服务名称", "width": 6 }, { "name": "所属厂商", "width": 6 }, { "name": "服务类型", "width": 4 }, { "name": "状态", "width": 4 }, { "name": "服务领域", "width": 4 }, + { "name": "行业覆盖", "width": 4 }, { "name": "计费方式", "width": 4 }, { "name": "创建时间", "width": 6 }, { "name": "更新时间", "width": 6 } diff --git a/tests/tasks/mail.task.json b/tests/tasks/mail.task.json new file mode 100644 index 00000000..5bf7bafd --- /dev/null +++ b/tests/tasks/mail.task.json @@ -0,0 +1,16 @@ +{ + "name": "发送邮件", + "worker_nums": "$ENV.SEND_MAIL_WORKER_NUMS", + "attempts": 3, + "attempt_after": 200, + "timeout": 2, + "size": 1000, + "process": "scripts.mail.Send", + "event": { + "next": "scripts.mail.NextID", + "add": "scripts.mail.OnAdd", + "success": "scripts.mail.OnSuccess", + "error": "scripts.mail.OnError", + "progress": "scripts.mail.OnProgress" + } +} diff --git a/tests/websockets/message.ws.json b/tests/websockets/message.ws.json new file mode 100644 index 00000000..0ef04577 --- /dev/null +++ b/tests/websockets/message.ws.json @@ -0,0 +1,18 @@ +{ + "name": "A WebSocket client", + "description": "A Chat WebSocket client", + "version": "0.10.0", + "url": "ws://127.0.0.1:5011/websocket/message", + "protocols": ["yao-message-01"], + "buffer": { "read": 1024, "write": 1024 }, + "limit": { "read-wait": 10, "pong-wait": 20, "max-message": 1024 }, + "timeout": 5, + "attempt_after": 200, + "attempts": 9, + "event": { + "data": "scripts.websocket.onData", + "closed": "scripts.websocket.onClosed", + "connected": "scripts.websocket.onConnected", + "error": "scripts.websocket.onError" + } +} diff --git a/tests/widgets/README.md b/tests/widgets/README.md new file mode 100644 index 00000000..18be262f --- /dev/null +++ b/tests/widgets/README.md @@ -0,0 +1,3 @@ +# Widgets + +## a customize low-code modules diff --git a/tests/widgets/dyform/README.md b/tests/widgets/dyform/README.md new file mode 100644 index 00000000..0c3894f7 --- /dev/null +++ b/tests/widgets/dyform/README.md @@ -0,0 +1,3 @@ +# DyForm + +## A form widget. users can design forms online diff --git a/tests/widgets/dyform/assets/README.md b/tests/widgets/dyform/assets/README.md new file mode 100644 index 00000000..226d49ab --- /dev/null +++ b/tests/widgets/dyform/assets/README.md @@ -0,0 +1,5 @@ +# Assets + +1. The assets folder's file will be copied to the `ui/.widgets/