├── .github
├── CODEOWNERS
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── dependabot.yml
└── workflows
│ ├── build.yml
│ ├── release.yml
│ └── release_pkg.yml
├── .gitignore
├── .go-version
├── .goreleaser.yml
├── .mergify.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── assets
├── charts
│ ├── chart.drawio
│ └── chart.png
└── img
│ └── gopher.png
├── beaver.go
├── bin
└── release.sh
├── cache
└── .gitignore
├── cmd
├── api.go
├── root.go
└── version.go
├── config.dist.yml
├── config.toml
├── core
├── api
│ ├── channel.go
│ ├── client.go
│ └── config.go
├── controller
│ ├── channel.go
│ ├── client.go
│ ├── config.go
│ ├── health.go
│ ├── home.go
│ ├── metrics.go
│ └── socket.go
├── driver
│ └── redis.go
├── middleware
│ ├── auth.go
│ ├── correlation.go
│ ├── cors.go
│ ├── log.go
│ └── metric.go
└── util
│ ├── helpers.go
│ ├── map.go
│ ├── token.go
│ └── validator.go
├── deployment
├── .gitkeep
├── docker
│ └── .gitkeep
└── linux
│ └── .gitkeep
├── go.mod
├── go.sum
├── renovate.json
├── techstack.md
├── techstack.yml
└── web
├── static
├── .gitkeep
├── css
│ └── app.css
├── img
│ └── logo.png
└── js
│ └── beaver.js
└── template
└── index.tmpl
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Docs: https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners
2 | * @clivern
3 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: # clivern
2 | custom: clivern.com/sponsor/
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 |
5 | ---
6 |
7 | **Describe the bug**
8 | A clear and concise description of what the bug is.
9 |
10 | **Development or production environment**
11 | - OS: [e.g. Ubuntu 18.04]
12 | - Go 1.13
13 |
14 | **Additional context**
15 | Add any other context about the problem here.
16 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 |
5 | ---
6 |
7 | **Is your feature request related to a problem? Please describe.**
8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
9 |
10 | **Describe the solution you'd like**
11 | A clear and concise description of what you want to happen.
12 |
13 | **Additional context**
14 | Add any other context or screenshots about the feature request here.
15 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "gomod"
9 | directory: "/"
10 | schedule:
11 | interval: "daily"
12 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | pull_request:
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | strategy:
11 | fail-fast: false
12 | matrix:
13 | go: ['1.18', '1.19', '1.20.4']
14 | name: Go ${{ matrix.go }} run
15 | steps:
16 | - uses: actions/checkout@v3
17 | - name: Setup go
18 | uses: actions/setup-go@v3
19 | with:
20 | go-version: ${{ matrix.go }}
21 |
22 | - name: Get dependencies
23 | run: |
24 | export PATH=${PATH}:`go env GOPATH`/bin
25 | make install_revive
26 |
27 | - name: Run make ci
28 | run: |
29 | export PATH=${PATH}:`go env GOPATH`/bin
30 | go get -t .
31 | make ci
32 | make integration
33 | git status
34 | git diff > diff.log
35 | cat diff.log
36 | git clean -fd
37 | git reset --hard
38 | make verify
39 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 | goreleaser:
10 | runs-on: ubuntu-latest
11 | steps:
12 | -
13 | name: Checkout
14 | uses: actions/checkout@v3
15 | with:
16 | fetch-depth: 0
17 | -
18 | name: Set up Go
19 | uses: actions/setup-go@v3
20 | with:
21 | go-version: 1.19
22 | -
23 | name: Run GoReleaser
24 | uses: goreleaser/goreleaser-action@v4
25 | with:
26 | version: latest
27 | args: release --rm-dist
28 | env:
29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30 |
--------------------------------------------------------------------------------
/.github/workflows/release_pkg.yml:
--------------------------------------------------------------------------------
1 | name: ReleasePkg
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 | release:
10 | runs-on: ubuntu-latest
11 | steps:
12 | -
13 | name: Checkout
14 | uses: actions/checkout@v3
15 | with:
16 | fetch-depth: 0
17 | -
18 | name: Set up Go
19 | uses: actions/setup-go@v3
20 | with:
21 | go-version: 1.19
22 |
23 | - name: Update checksum database
24 | run: |
25 | ./bin/release.sh
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, build with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # dist dir
15 | dist
16 |
17 | sync
18 |
19 | *.db
20 |
21 | config.prod.yml
--------------------------------------------------------------------------------
/.go-version:
--------------------------------------------------------------------------------
1 | 1.20.4
2 |
--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | # This is an example goreleaser.yaml file with some sane defaults.
2 | # Make sure to check the documentation at http://goreleaser.com
3 | ---
4 | archives:
5 | -
6 | replacements:
7 | 386: i386
8 | amd64: x86_64
9 | darwin: Darwin
10 | linux: Linux
11 | windows: Windows
12 | files:
13 | - LICENSE
14 | - README.md
15 | - config.dist.yml
16 | before:
17 | hooks:
18 | - "go mod download"
19 | - "go generate ./..."
20 | builds:
21 | -
22 | env:
23 | - CGO_ENABLED=0
24 | goos:
25 | - linux
26 | - darwin
27 | - windows
28 |
29 | changelog:
30 | filters:
31 | exclude:
32 | - "^docs:"
33 | - "^test:"
34 | sort: asc
35 | checksum:
36 | name_template: checksums.txt
37 | snapshot:
38 | name_template: "{{ .Tag }}-next"
39 | project_name: beaver
40 |
--------------------------------------------------------------------------------
/.mergify.yml:
--------------------------------------------------------------------------------
1 | ---
2 | pull_request_rules:
3 | -
4 | actions:
5 | merge:
6 | method: squash
7 | conditions:
8 | - author!=Clivern
9 | - approved-reviews-by=Clivern
10 | - label=release
11 | name: "Automatic Merge 🚀"
12 | -
13 | actions:
14 | merge:
15 | method: merge
16 | conditions:
17 | - author=Clivern
18 | - label=release
19 | name: "Automatic Merge 🚀"
20 | -
21 | actions:
22 | merge:
23 | method: squash
24 | conditions:
25 | - "author=renovate[bot]"
26 | - label=release
27 | name: "Automatic Merge for Renovate PRs 🚀"
28 | -
29 | actions:
30 | comment:
31 | message: "Nice! PR merged successfully."
32 | conditions:
33 | - merged
34 | name: "Merge Done 🚀"
35 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at hello@clivern.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contributing
2 |
3 | - With issues:
4 | - Use the search tool before opening a new issue.
5 | - Please provide source code and commit sha if you found a bug.
6 | - Review existing issues and provide feedback or react to them.
7 |
8 | - With pull requests:
9 | - Open your pull request against `main`
10 | - Your pull request should have no more than two commits, if not you should squash them.
11 | - It should pass all tests in the available continuous integrations systems such as TravisCI.
12 | - You should add/modify tests to cover your proposed code changes.
13 | - If your pull request contains a new feature, please document it on the README.
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Clivern
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | GO ?= go
2 | GOFMT ?= $(GO)fmt
3 | NPM ?= npm
4 | NPX ?= npx
5 | RHINO ?= rhino
6 | pkgs = ./...
7 | PKGER ?= pkger
8 |
9 |
10 | help: Makefile
11 | @echo
12 | @echo " Choose a command run in Beaver:"
13 | @echo
14 | @sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /'
15 | @echo
16 |
17 |
18 | ## install_revive: Install revive for linting.
19 | .PHONY: install_revive
20 | install_revive:
21 | @echo ">> ============= Install Revive ============= <<"
22 | $(GO) install github.com/mgechev/revive@latest
23 |
24 |
25 | ## style: Check code style.
26 | .PHONY: style
27 | style:
28 | @echo ">> ============= Checking Code Style ============= <<"
29 | @fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \
30 | if [ -n "$${fmtRes}" ]; then \
31 | echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \
32 | echo "Please ensure you are using $$($(GO) version) for formatting code."; \
33 | exit 1; \
34 | fi
35 |
36 |
37 | ## check_license: Check if license header on all files.
38 | .PHONY: check_license
39 | check_license:
40 | @echo ">> ============= Checking License Header ============= <<"
41 | @licRes=$$(for file in $$(find . -type f -iname '*.go' ! -path './vendor/*') ; do \
42 | awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \
43 | done); \
44 | if [ -n "$${licRes}" ]; then \
45 | echo "license header checking failed:"; echo "$${licRes}"; \
46 | exit 1; \
47 | fi
48 |
49 |
50 | ## test_short: Run test cases with short flag.
51 | .PHONY: test_short
52 | test_short:
53 | @echo ">> ============= Running Short Tests ============= <<"
54 | $(GO) clean -testcache
55 | $(GO) test -mod=readonly -short $(pkgs)
56 |
57 |
58 | ## test: Run test cases.
59 | .PHONY: test
60 | test:
61 | @echo ">> ============= Running All Tests ============= <<"
62 | $(GO) clean -testcache
63 | $(GO) test -mod=readonly -run=Unit -bench=. -benchmem -v -cover $(pkgs)
64 |
65 |
66 | ## integration: Run integration test cases (Requires redis)
67 | .PHONY: integration
68 | integration:
69 | @echo ">> ============= Running All Tests ============= <<"
70 | $(GO) clean -testcache
71 | $(GO) test -mod=readonly -run=Integration -bench=. -benchmem -v -cover $(pkgs)
72 |
73 |
74 | ## lint: Lint the code.
75 | .PHONY: lint
76 | lint:
77 | @echo ">> ============= Lint All Files ============= <<"
78 | revive -config config.toml -exclude vendor/... -formatter friendly ./...
79 |
80 |
81 | ## verify: Verify dependencies
82 | .PHONY: verify
83 | verify:
84 | @echo ">> ============= List Dependencies ============= <<"
85 | $(GO) list -m all
86 | @echo ">> ============= Verify Dependencies ============= <<"
87 | $(GO) mod verify
88 |
89 |
90 | ## format: Format the code.
91 | .PHONY: format
92 | format:
93 | @echo ">> ============= Formatting Code ============= <<"
94 | $(GO) fmt $(pkgs)
95 |
96 |
97 | ## vet: Examines source code and reports suspicious constructs.
98 | .PHONY: vet
99 | vet:
100 | @echo ">> ============= Vetting Code ============= <<"
101 | $(GO) vet $(pkgs)
102 |
103 |
104 | ## coverage: Create HTML coverage report
105 | .PHONY: coverage
106 | coverage:
107 | @echo ">> ============= Coverage ============= <<"
108 | rm -f coverage.html cover.out
109 | $(GO) test -mod=readonly -coverprofile=cover.out $(pkgs)
110 | go tool cover -html=cover.out -o coverage.html
111 |
112 |
113 | ## run: Run the API Server
114 | .PHONY: run
115 | run:
116 | @echo ">> ============= Run Tower ============= <<"
117 | $(GO) run beaver.go api -c config.dist.yml
118 |
119 |
120 | ## ci: Run all CI tests.
121 | .PHONY: ci
122 | ci: style check_license test vet lint
123 | @echo "\n==> All quality checks passed"
124 |
125 |
126 | .PHONY: help
127 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Beaver
4 | A Real Time Messaging Server.
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | Beaver is a real-time messaging server. With beaver you can easily build scalable in-app notifications, realtime graphs, multiplayer games, chat applications, geotracking and more in web applications and mobile apps.
32 |
33 |
34 | ## Documentation
35 |
36 | #### Run Beaver on Ubuntu
37 |
38 | Download [the latest beaver binary](https://github.com/Clivern/Beaver/releases). Make it executable from everywhere.
39 |
40 | ```zsh
41 | $ export BEAVER_LATEST_VERSION=$(curl --silent "https://api.github.com/repos/Clivern/Beaver/releases/latest" | jq '.tag_name' | sed -E 's/.*"([^"]+)".*/\1/' | tr -d v)
42 |
43 | $ curl -sL https://github.com/Clivern/Beaver/releases/download/v{$BEAVER_LATEST_VERSION}/beaver_{$BEAVER_LATEST_VERSION}_Linux_x86_64.tar.gz | tar xz
44 | ```
45 |
46 | Then install `redis` cluster or a single node. Update the following config file with redis configs.
47 |
48 |
49 | Create the configs file `config.yml` from `config.dist.yml`. Something like the following:
50 |
51 | ```yaml
52 | # App configs
53 | app:
54 | # Env mode (dev or prod)
55 | mode: ${BEAVER_APP_MODE:-prod}
56 | # HTTP port
57 | port: ${BEAVER_API_PORT:-8080}
58 | # Hostname
59 | hostname: ${BEAVER_API_HOSTNAME:-127.0.0.1}
60 | # TLS configs
61 | tls:
62 | status: ${BEAVER_API_TLS_STATUS:-off}
63 | pemPath: ${BEAVER_API_TLS_PEMPATH:-cert/server.pem}
64 | keyPath: ${BEAVER_API_TLS_KEYPATH:-cert/server.key}
65 |
66 | # API Configs
67 | api:
68 | key: ${BEAVER_API_KEY:-6c68b836-6f8e-465e-b59f-89c1db53afca}
69 |
70 | # Beaver Secret
71 | secret: ${BEAVER_SECRET:-sWUhHRcs4Aqa0MEnYwbuQln3EW8CZ0oD}
72 |
73 | # Runtime, Requests/Response and Beaver Metrics
74 | metrics:
75 | prometheus:
76 | # Route for the metrics endpoint
77 | endpoint: ${BEAVER_METRICS_PROM_ENDPOINT:-/metrics}
78 |
79 | # Application Database
80 | database:
81 | # Database driver
82 | driver: ${BEAVER_DB_DRIVER:-redis}
83 |
84 | # Redis Configs
85 | redis:
86 | # Redis address
87 | address: ${BEAVER_DB_REDIS_ADDR:-localhost:6379}
88 | # Redis password
89 | password: ${BEAVER_DB_REDIS_PASSWORD:- }
90 | # Redis database
91 | db: ${BEAVER_DB_REDIS_DB:-0}
92 |
93 | # Log configs
94 | log:
95 | # Log level, it can be debug, info, warn, error, panic, fatal
96 | level: ${BEAVER_LOG_LEVEL:-info}
97 | # Output can be stdout or abs path to log file /var/logs/beaver.log
98 | output: ${BEAVER_LOG_OUTPUT:-stdout}
99 | # Format can be json
100 | format: ${BEAVER_LOG_FORMAT:-json}
101 | ```
102 |
103 | The run the `beaver` with `systemd`
104 |
105 | ```zsh
106 | $ beaver api -c /path/to/config.yml
107 | ```
108 |
109 | ### API Endpoints
110 |
111 | Create a Config `app_name`:
112 |
113 | ```bash
114 | $ curl -X POST \
115 | -H "Content-Type: application/json" \
116 | -H "X-API-Key: 6c68b836-6f8e-465e-b59f-89c1db53afca" \
117 | -d '{"key":"app_name","value":"Beaver"}' \
118 | "http://localhost:8080/api/config"
119 | ```
120 |
121 | Get a Config `app_name`:
122 |
123 | ```bash
124 | $ curl -X GET \
125 | -H "Content-Type: application/json" \
126 | -H "X-API-Key: 6c68b836-6f8e-465e-b59f-89c1db53afca" \
127 | "http://localhost:8080/api/config/app_name"
128 |
129 | {"key":"app_name","value":"Beaver"}
130 | ```
131 |
132 | Update a Config `app_name`:
133 |
134 | ```bash
135 | $ curl -X PUT \
136 | -H "Content-Type: application/json" \
137 | -H "X-API-Key: 6c68b836-6f8e-465e-b59f-89c1db53afca" \
138 | -d '{"value":"Beaver"}' \
139 | "http://localhost:8080/api/config/app_name"
140 | ```
141 |
142 | Delete a Config `app_name`:
143 |
144 | ```bash
145 | $ curl -X DELETE \
146 | -H "Content-Type: application/json" \
147 | -H "X-API-Key: 6c68b836-6f8e-465e-b59f-89c1db53afca" \
148 | "http://localhost:8080/api/config/app_name"
149 | ```
150 |
151 | Create a Channel:
152 |
153 | ```bash
154 | # Private Channel
155 | $ curl -X POST \
156 | -H 'Content-Type: application/json' \
157 | -H 'X-API-Key: 6c68b836-6f8e-465e-b59f-89c1db53afca' \
158 | -d '{"name": "app_x_chatroom_1", "type": "private"}' \
159 | 'http://localhost:8080/api/channel'
160 |
161 | # Public Channel
162 | $ curl -X POST \
163 | -H 'Content-Type: application/json' \
164 | -H 'X-API-Key: 6c68b836-6f8e-465e-b59f-89c1db53afca' \
165 | -d '{"name": "app_y_chatroom_1", "type": "public"}' \
166 | 'http://localhost:8080/api/channel'
167 |
168 | # Presence Channel
169 | $ curl -X POST \
170 | -H 'Content-Type: application/json' \
171 | -H 'X-API-Key: 6c68b836-6f8e-465e-b59f-89c1db53afca' \
172 | -d '{"name": "app_z_chatroom_5", "type": "presence"}' \
173 | 'http://localhost:8080/api/channel'
174 | ```
175 |
176 | Get a Channel:
177 |
178 | ```bash
179 | $ curl -X GET \
180 | -H 'Content-Type: application/json' \
181 | -H 'X-API-Key: 6c68b836-6f8e-465e-b59f-89c1db53afca' \
182 | -d '' \
183 | 'http://localhost:8080/api/channel/app_x_chatroom_1'
184 | {
185 | "created_at":1545573214,
186 | "listeners_count":0,
187 | "name":"app_x_chatroom_1",
188 | "subscribers_count":0,
189 | "type":"private",
190 | "updated_at":1545573214
191 | }
192 |
193 | $ curl -X GET \
194 | -H 'Content-Type: application/json' \
195 | -H 'X-API-Key: 6c68b836-6f8e-465e-b59f-89c1db53afca' \
196 | -d '' \
197 | 'http://localhost:8080/api/channel/app_y_chatroom_1'
198 | {
199 | "created_at":1545573219,
200 | "listeners_count":0,
201 | "name":"app_y_chatroom_1",
202 | "subscribers_count":0,
203 | "type":"public",
204 | "updated_at":1545573219
205 | }
206 |
207 | $ curl -X GET \
208 | -H 'Content-Type: application/json' \
209 | -H 'X-API-Key: 6c68b836-6f8e-465e-b59f-89c1db53afca' \
210 | -d '' \
211 | 'http://localhost:8080/api/channel/app_z_chatroom_5'
212 | {
213 | "created_at": 1545573225,
214 | "listeners": null,
215 | "listeners_count": 0,
216 | "name": "app_z_chatroom_5",
217 | "subscribers": null,
218 | "subscribers_count": 0,
219 | "type": "presence",
220 | "updated_at": 1545573225
221 | }
222 | ```
223 |
224 | Update a Channel `app_y_chatroom_1`:
225 |
226 | ```bash
227 | $ curl -X PUT \
228 | -H 'Content-Type: application/json' \
229 | -H 'X-API-Key: 6c68b836-6f8e-465e-b59f-89c1db53afca' \
230 | -d '{"type": "private"}' \
231 | 'http://localhost:8080/api/channel/app_y_chatroom_1'
232 | ```
233 |
234 | Delete a Channel `app_y_chatroom_1`:
235 |
236 | ```bash
237 | $ curl -X DELETE \
238 | -H 'Content-Type: application/json' \
239 | -H 'X-API-Key: 6c68b836-6f8e-465e-b59f-89c1db53afca' \
240 | -d '' \
241 | 'http://localhost:8080/api/channel/app_y_chatroom_1'
242 | ```
243 |
244 | Create a Client and add to `app_x_chatroom_1` Channel:
245 |
246 | ```bash
247 | $ curl -X POST \
248 | -H 'Content-Type: application/json' \
249 | -H 'X-API-Key: 6c68b836-6f8e-465e-b59f-89c1db53afca' \
250 | -d '{"channels": ["app_x_chatroom_1"]}' \
251 | 'http://localhost:8080/api/client'
252 | {
253 | "channels": [
254 | "app_x_chatroom_1"
255 | ],
256 | "created_at": 1545575142,
257 | "id": "69775af3-5f68-4725-8162-09cab63e8427",
258 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiNjk3NzVhZjMtNWY2OC00NzI1LTgxNjItMDljYWI2M2U4NDI3QDE1NDU1NzUxNDIiLCJ0aW1lc3RhbXAiOjE1NDU1NzUxNDJ9.EqL-nWwu5p7hJXWrKdZN3Ds2cxWVjNYmeP1mbl562nU",
259 | "updated_at": 1545575142
260 | }
261 | ```
262 |
263 | Get a Client `69775af3-5f68-4725-8162-09cab63e8427`:
264 |
265 | ```bash
266 | $ curl -X GET \
267 | -H 'Content-Type: application/json' \
268 | -H 'X-API-Key: 6c68b836-6f8e-465e-b59f-89c1db53afca' \
269 | -d '' \
270 | 'http://localhost:8080/api/client/69775af3-5f68-4725-8162-09cab63e8427'
271 | {
272 | "channels": [
273 | "app_x_chatroom_1"
274 | ],
275 | "created_at": 1545575142,
276 | "id": "69775af3-5f68-4725-8162-09cab63e8427",
277 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiNjk3NzVhZjMtNWY2OC00NzI1LTgxNjItMDljYWI2M2U4NDI3QDE1NDU1NzUxNDIiLCJ0aW1lc3RhbXAiOjE1NDU1NzUxNDJ9.EqL-nWwu5p7hJXWrKdZN3Ds2cxWVjNYmeP1mbl562nU",
278 | "updated_at": 1545575142
279 | }
280 | ```
281 |
282 | Subscribe a Client `69775af3-5f68-4725-8162-09cab63e8427` to a Channel `app_z_chatroom_5`:
283 |
284 | ```bash
285 | $ curl -X PUT \
286 | -H 'Content-Type: application/json' \
287 | -H 'X-API-Key: 6c68b836-6f8e-465e-b59f-89c1db53afca' \
288 | -d '{"channels": ["app_z_chatroom_5"]}' \
289 | 'http://localhost:8080/api/client/69775af3-5f68-4725-8162-09cab63e8427/subscribe'
290 | ```
291 |
292 | Unsubscribe a Client `69775af3-5f68-4725-8162-09cab63e8427` from a Channel `app_z_chatroom_5`:
293 |
294 | ```bash
295 | $ curl -X PUT \
296 | -H 'Content-Type: application/json' \
297 | -H 'X-API-Key: 6c68b836-6f8e-465e-b59f-89c1db53afca' \
298 | -d '{"channels": ["app_z_chatroom_5"]}' \
299 | 'http://localhost:8080/api/client/69775af3-5f68-4725-8162-09cab63e8427/unsubscribe'
300 | ```
301 |
302 | Delete a Client:
303 |
304 | ```bash
305 | $ curl -X DELETE \
306 | -H 'Content-Type: application/json' \
307 | -H 'X-API-Key: 6c68b836-6f8e-465e-b59f-89c1db53afca' \
308 | -d '' \
309 | 'http://localhost:8080/api/client/69775af3-5f68-4725-8162-09cab63e8427'
310 | ```
311 |
312 | Publish to a Channel `app_x_chatroom_1`:
313 |
314 | ```bash
315 | $ curl -X POST \
316 | -H 'Content-Type: application/json' \
317 | -H 'X-API-Key: 6c68b836-6f8e-465e-b59f-89c1db53afca' \
318 | -d '{"channel": "app_x_chatroom_1", "data": "{\"message\": \"Hello World\"}"}' \
319 | 'http://localhost:8080/api/publish'
320 | ```
321 |
322 | Broadcast to Channels `["app_x_chatroom_1"]`:
323 |
324 | ```bash
325 | $ curl -X POST \
326 | -H 'Content-Type: application/json' \
327 | -H 'X-API-Key: 6c68b836-6f8e-465e-b59f-89c1db53afca' \
328 | -d '{"channels": ["app_x_chatroom_1"], "data": "{\"message\": \"Hello World\"}"}' \
329 | 'http://localhost:8080/api/broadcast'
330 | ```
331 |
332 | Sample Frontend Client
333 |
334 | ```js
335 | function Socket(url){
336 | ws = new WebSocket(url);
337 | ws.onmessage = function(e) { console.log(e); };
338 | ws.onclose = function(){
339 | // Try to reconnect in 5 seconds
340 | setTimeout(function(){Socket(url)}, 5000);
341 | };
342 | }
343 |
344 | Socket("ws://localhost:8080/ws/$ID/$TOKEN");
345 | ```
346 |
347 |
348 | ## Client:
349 |
350 | - [Go Client](https://github.com/domgolonka/beavergo) Thanks [@domgolonka](https://github.com/domgolonka)
351 |
352 |
353 | ## Versioning
354 |
355 | For transparency into our release cycle and in striving to maintain backward compatibility, Beaver is maintained under the [Semantic Versioning guidelines](https://semver.org/) and release process is predictable and business-friendly.
356 |
357 | See the [Releases section of our GitHub project](https://github.com/clivern/beaver/releases) for changelogs for each release version of Beaver. It contains summaries of the most noteworthy changes made in each release.
358 |
359 |
360 | ## Bug tracker
361 |
362 | If you have any suggestions, bug reports, or annoyances please report them to our issue tracker at https://github.com/clivern/beaver/issues
363 |
364 |
365 | ## Security Issues
366 |
367 | If you discover a security vulnerability within Beaver, please send an email to [hello@clivern.com](mailto:hello@clivern.com)
368 |
369 |
370 | ## Contributing
371 |
372 | We are an open source, community-driven project so please feel free to join us. see the [contributing guidelines](CONTRIBUTING.md) for more details.
373 |
374 |
375 | ## License
376 |
377 | © 2018, Clivern. Released under [MIT License](https://opensource.org/licenses/mit-license.php).
378 |
379 | **Beaver** is authored and maintained by [@Clivern](http://github.com/clivern).
380 |
--------------------------------------------------------------------------------
/assets/charts/chart.drawio:
--------------------------------------------------------------------------------
1 | 7VpbU9s6EP41PJbxNZdHkkDpmXImc+jQ9omRbcXRQbZ8ZAWS/vqzsuWLLBNCm0CaKcwk1upia79v17urnLnTZP2Ro2x5wyJMzxwrWp+5szPHsW1rAF9SsiklQ3dYCmJOIjWoEdySH1gJLSVdkQjn2kDBGBUk04UhS1McCk2GOGdP+rAFo/pdMxRjQ3AbImpKv5JILEvpyLca+TUm8VLUG1Y9CaoGK0G+RBF7aoncyzN3yhkT5VWynmIqlVfppZx39Uxv/WAcp2KXCdMRtZL1X3ff58F8lrPN6mpIP7iOejixqXaMI1CAajIulixmKaKXjXTC2SqNsFzWglYz5jNjGQhtEP6LhdgoNNFKMBAtRUJVL14T8U1Nl9ff5fW5r1qzdatrtqkaqeCbb9UCstGaJZvNtKJVzVsQSqeMMl7szl0sFk4YgjwXnD3gVk80CAb+AHpMxSpd52zFQ7xFmxVpBeIxFlsG+uU4qerWHRRuHzFLMGwBBnBMkSCPOheRonRcj2tQhwsF/CtIUD32I6IrdSuDFQ3mUv9PSyLwbYYKbTyB5ev47lHnj5gLvN6qJNXrDdU2lKfxKsN7auzWHijZsmWzY+tAevVPQa322DkytbqD9/RZtuaznO1Oy0AMD55BbDgOLOuXnI+zo+9xjsv5VPHAMaD5G4LpHReYo+MBc/hKNA8YF+yKZvUi3h+axdQLztGmNSBjJBV5a+W5FLRd/lBz+a7fiSw7433X2jYeLsonaHhVb+Xnqea8/HJFeVamBAuylvyaZJgTuC+WyMItIIfA80bUphSiJE7hOgSsi746epfciVC+rAm7YKlo0cVzhsPJBOQUBZhOUPgQFwTXuQZ/aqpite3Im8Crn6Txl4L3LghIUqQn1feMJDEoi5IAPkmQwCfKMkpCIANL87J5n2MOr/fz/DHeUwTg6fBC+mNEAH0BwOBQAcDQgP4fHBHYvnVbbN0gAmxUvICvVAfokV6ojoREUemdcE5+oKBYSsKtjAfW9Sdn/kyuBQ4pVyhWoKp79yRpr1Y/5Mya+n3bNtQ/7lG/cyj1jwz1XzQkhA7JeZxGp4WCbfm6EYx7wmD3LWEY/3GA0gFGSCD4ClkKYOYFB+8FRyTF0X2EM8o2ONqfL/QdnQaeZ9LA72GBfygW2OZ7cIJR4QStv1mET8sK/Y4VvrsvtAd/rFBa4QqCDhl/BLLius/gw/P14GNk2tubxh62GXwYgLOVoOB/pnVJugcf+L+St53EHEUEN30pS3FfGlJP0FCvTPWzRHkOple8g91ZwIRgSY8tCwmqyat2lanDhrYNy1sadAZOZrIzWcfyDOAcPeXeObiKIiv6FMrnkZ6jvNJHJSwgFN+HlBQM0ZIypYg9cMjtRFDvzSFnh3orBFAX8vhCgkRRnpMyN0VcmOJfKhHWdXXr3B977dq6fW4NRy9U14tWy3nVebZasZ2Rb03Hn4V5f0X3nWslO77LX5eEm1lzt15td/hW7lzN2pJ+e2N9IbsbbJSaMRbaWyZeGeSx0XnUobN9BHT+2fLszobwbnT29kVnq0Pnrh8+NJ09g85fMUQ61rR8S51UQO15ekBt9+Qzo7cMqB3z0OymCBJOFAC3SuAqAFzPAOBNMxrHzGiA/jkLH/Auqtc9s4rhesK63SHqOxTVjwx0TGSvQCoOHvh7SkO8kQaT23MK6r0lTK5Z/plyjERjJ5COTWVjiSANodAaoETqLg3yrFDKgErsAg5XsbxCsmZnzVf5sqgdCLJoCsunZHJ1YF9hafsGlr0n2u6hwPTMhGDO2SOR5RurLudcs1woTJUjtL6ArcmSaw1u+QmSTzMT4C9MzlVZqfwdVXv10ysWOd2SrW2eW/Qa7eFwNiPl39i3DvfkW31bt8e+86VD+dZFEjjXKZrf3l2x//I7P5/f3H3YIZ8xiiAtmHqI3gWkKqh1ay19BbYkDxFY5tUUhUt8X5x03YN3iFah0MpshsJ7YHneVjy9RFK/715IHjzn1RhAs/nRYxm0Nz8ddS//Bw==
--------------------------------------------------------------------------------
/assets/charts/chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clivern/Beaver/bd68afd9d44386b789e46d303f7f4a865ac4cc68/assets/charts/chart.png
--------------------------------------------------------------------------------
/assets/img/gopher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clivern/Beaver/bd68afd9d44386b789e46d303f7f4a865ac4cc68/assets/img/gopher.png
--------------------------------------------------------------------------------
/beaver.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package main
6 |
7 | import (
8 | "github.com/clivern/beaver/cmd"
9 | )
10 |
11 | var (
12 | version = "dev"
13 | commit = "none"
14 | date = "unknown"
15 | builtBy = "unknown"
16 | )
17 |
18 | func main() {
19 | cmd.Version = version
20 | cmd.Commit = commit
21 | cmd.Date = date
22 | cmd.BuiltBy = builtBy
23 |
24 | cmd.Execute()
25 | }
26 |
--------------------------------------------------------------------------------
/bin/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Fetch latest version
4 | export LATEST_VERSION=$(curl --silent "https://api.github.com/repos/clivern/beaver/releases/latest" | jq '.tag_name' | sed -E 's/.*"([^"]+)".*/\1/')
5 |
6 | # Update go checksum database (sum.golang.org) immediately after release
7 | curl --silent https://sum.golang.org/lookup/github.com/clivern/beaver@{$LATEST_VERSION}
8 |
--------------------------------------------------------------------------------
/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/cmd/api.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmd
6 |
7 | import (
8 | "bytes"
9 | "fmt"
10 | "io"
11 | "io/ioutil"
12 | "net/http"
13 | "os"
14 | "path/filepath"
15 | "strconv"
16 | "strings"
17 |
18 | "github.com/clivern/beaver/core/controller"
19 | "github.com/clivern/beaver/core/middleware"
20 | "github.com/clivern/beaver/core/util"
21 |
22 | "github.com/drone/envsubst"
23 | "github.com/gin-gonic/gin"
24 | log "github.com/sirupsen/logrus"
25 | "github.com/spf13/cobra"
26 | "github.com/spf13/viper"
27 | )
28 |
29 | var towerCmd = &cobra.Command{
30 | Use: "api",
31 | Short: "Start beaver api server",
32 | Run: func(cmd *cobra.Command, args []string) {
33 | configUnparsed, err := ioutil.ReadFile(config)
34 |
35 | if err != nil {
36 | panic(fmt.Sprintf(
37 | "Error while reading config file [%s]: %s",
38 | config,
39 | err.Error(),
40 | ))
41 | }
42 |
43 | configParsed, err := envsubst.EvalEnv(string(configUnparsed))
44 |
45 | if err != nil {
46 | panic(fmt.Sprintf(
47 | "Error while parsing config file [%s]: %s",
48 | config,
49 | err.Error(),
50 | ))
51 | }
52 |
53 | viper.SetConfigType("yaml")
54 | err = viper.ReadConfig(bytes.NewBufferString(configParsed))
55 |
56 | if err != nil {
57 | panic(fmt.Sprintf(
58 | "Error while loading configs [%s]: %s",
59 | config,
60 | err.Error(),
61 | ))
62 | }
63 |
64 | viper.SetDefault("app.name", util.GenerateUUID4())
65 |
66 | if viper.GetString("app.log.output") != "stdout" {
67 | dir, _ := filepath.Split(viper.GetString("app.log.output"))
68 |
69 | if !util.DirExists(dir) {
70 | if _, err := util.EnsureDir(dir, 775); err != nil {
71 | panic(fmt.Sprintf(
72 | "Directory [%s] creation failed with error: %s",
73 | dir,
74 | err.Error(),
75 | ))
76 | }
77 | }
78 |
79 | if !util.FileExists(viper.GetString("app.log.output")) {
80 | f, err := os.Create(viper.GetString("app.log.output"))
81 | if err != nil {
82 | panic(fmt.Sprintf(
83 | "Error while creating log file [%s]: %s",
84 | viper.GetString("app.log.output"),
85 | err.Error(),
86 | ))
87 | }
88 | defer f.Close()
89 | }
90 | }
91 |
92 | if viper.GetString("app.log.output") == "stdout" {
93 | gin.DefaultWriter = os.Stdout
94 | log.SetOutput(os.Stdout)
95 | } else {
96 | f, _ := os.Create(viper.GetString("app.log.output"))
97 | gin.DefaultWriter = io.MultiWriter(f)
98 | log.SetOutput(f)
99 | }
100 |
101 | lvl := strings.ToLower(viper.GetString("app.log.level"))
102 | level, err := log.ParseLevel(lvl)
103 |
104 | if err != nil {
105 | level = log.InfoLevel
106 | }
107 |
108 | log.SetLevel(level)
109 |
110 | if viper.GetString("app.mode") == "prod" {
111 | gin.SetMode(gin.ReleaseMode)
112 | gin.DefaultWriter = ioutil.Discard
113 | gin.DisableConsoleColor()
114 | }
115 |
116 | if viper.GetString("app.log.format") == "json" {
117 | log.SetFormatter(&log.JSONFormatter{})
118 | } else {
119 | log.SetFormatter(&log.TextFormatter{})
120 | }
121 |
122 | r := gin.Default()
123 |
124 | r.Use(middleware.Cors())
125 | r.Use(middleware.Correlation())
126 | r.Use(middleware.Logger())
127 | r.Use(middleware.Metric())
128 | r.Use(middleware.Auth())
129 |
130 | r.GET("/favicon.ico", func(c *gin.Context) {
131 | c.String(http.StatusNoContent, "")
132 | })
133 |
134 | r.GET("/", controller.Home)
135 |
136 | r.GET("/_health", controller.Health)
137 |
138 | r.GET(
139 | viper.GetString("app.metrics.prometheus.endpoint"),
140 | gin.WrapH(controller.Metrics()),
141 | )
142 |
143 | r.GET("/api/channel/:name", controller.GetChannelByName)
144 | r.POST("/api/channel", controller.CreateChannel)
145 | r.DELETE("/api/channel/:name", controller.DeleteChannelByName)
146 | r.PUT("/api/channel/:name", controller.UpdateChannelByName)
147 |
148 | r.GET("/api/client/:id", controller.GetClientByID)
149 | r.POST("/api/client", controller.CreateClient)
150 | r.DELETE("/api/client/:id", controller.DeleteClientByID)
151 | r.PUT("/api/client/:id/unsubscribe", controller.Unsubscribe)
152 | r.PUT("/api/client/:id/subscribe", controller.Subscribe)
153 |
154 | r.GET("/api/config/:key", controller.GetConfigByKey)
155 | r.POST("/api/config", controller.CreateConfig)
156 | r.DELETE("/api/config/:key", controller.DeleteConfigByKey)
157 | r.PUT("/api/config/:key", controller.UpdateConfigByKey)
158 |
159 | socket := &controller.Websocket{}
160 | socket.Init()
161 |
162 | r.GET("/ws/:id/:token", func(c *gin.Context) {
163 | socket.HandleConnections(
164 | c.Writer,
165 | c.Request,
166 | c.Param("id"),
167 | c.Param("token"),
168 | )
169 | })
170 |
171 | r.POST("/api/broadcast", func(c *gin.Context) {
172 | rawBody, err := c.GetRawData()
173 | if err != nil {
174 | c.JSON(http.StatusBadRequest, gin.H{
175 | "status": "error",
176 | "error": "Invalid request",
177 | })
178 | return
179 | }
180 | socket.BroadcastAction(c, rawBody)
181 | })
182 |
183 | r.POST("/api/publish", func(c *gin.Context) {
184 | rawBody, err := c.GetRawData()
185 | if err != nil {
186 | c.JSON(http.StatusBadRequest, gin.H{
187 | "status": "error",
188 | "error": "Invalid request",
189 | })
190 | return
191 | }
192 | socket.PublishAction(c, rawBody)
193 | })
194 |
195 | go socket.HandleMessages()
196 |
197 | var runerr error
198 |
199 | if viper.GetBool("app.tls.status") {
200 | runerr = r.RunTLS(
201 | fmt.Sprintf(":%s", strconv.Itoa(viper.GetInt("app.port"))),
202 | viper.GetString("app.tls.pemPath"),
203 | viper.GetString("app.tls.keyPath"),
204 | )
205 | } else {
206 | runerr = r.Run(
207 | fmt.Sprintf(":%s", strconv.Itoa(viper.GetInt("app.port"))),
208 | )
209 | }
210 |
211 | if runerr != nil {
212 | panic(runerr.Error())
213 | }
214 | },
215 | }
216 |
217 | func init() {
218 | towerCmd.Flags().StringVarP(
219 | &config,
220 | "config",
221 | "c",
222 | "config.prod.yml",
223 | "Absolute path to config file (required)",
224 | )
225 | towerCmd.MarkFlagRequired("config")
226 | rootCmd.AddCommand(towerCmd)
227 | }
228 |
--------------------------------------------------------------------------------
/cmd/root.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmd
6 |
7 | import (
8 | "fmt"
9 | "os"
10 |
11 | "github.com/spf13/cobra"
12 | )
13 |
14 | var config string
15 |
16 | var rootCmd = &cobra.Command{
17 | Use: "beaver",
18 | Short: `🐺 A Real time messaging system to build a scalable in-app notifications,
19 | multiplayer games, chat apps in web and mobile apps.
20 |
21 | If you have any suggestions, bug reports, or annoyances please report
22 | them to our issue tracker at `,
23 | }
24 |
25 | // Execute runs cmd tool
26 | func Execute() {
27 | if err := rootCmd.Execute(); err != nil {
28 | fmt.Println(err)
29 | os.Exit(1)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/cmd/version.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmd
6 |
7 | import (
8 | "fmt"
9 |
10 | "github.com/spf13/cobra"
11 | )
12 |
13 | var (
14 | // Version buildinfo item
15 | Version = "dev"
16 | // Commit buildinfo item
17 | Commit = "none"
18 | // Date buildinfo item
19 | Date = "unknown"
20 | // BuiltBy buildinfo item
21 | BuiltBy = "unknown"
22 | )
23 |
24 | var versionCmd = &cobra.Command{
25 | Use: "version",
26 | Short: "Print the version number",
27 | Run: func(cmd *cobra.Command, args []string) {
28 | fmt.Println(
29 | fmt.Sprintf(
30 | `Current Beaver Version %v Commit %v, Built @%v By %v.`,
31 | Version,
32 | Commit,
33 | Date,
34 | BuiltBy,
35 | ),
36 | )
37 | },
38 | }
39 |
40 | func init() {
41 | rootCmd.AddCommand(versionCmd)
42 | }
43 |
--------------------------------------------------------------------------------
/config.dist.yml:
--------------------------------------------------------------------------------
1 | # App configs
2 | app:
3 | # Env mode (dev or prod)
4 | mode: ${BEAVER_APP_MODE:-dev}
5 | # HTTP port
6 | port: ${BEAVER_API_PORT:-8080}
7 | # Hostname
8 | hostname: ${BEAVER_API_HOSTNAME:-127.0.0.1}
9 | # TLS configs
10 | tls:
11 | status: ${BEAVER_API_TLS_STATUS:-off}
12 | pemPath: ${BEAVER_API_TLS_PEMPATH:-cert/server.pem}
13 | keyPath: ${BEAVER_API_TLS_KEYPATH:-cert/server.key}
14 |
15 | # API Configs
16 | api:
17 | key: ${BEAVER_API_KEY:-6c68b836-6f8e-465e-b59f-89c1db53afca}
18 |
19 | # Beaver Secret
20 | secret: ${BEAVER_SECRET:-sWUhHRcs4Aqa0MEnYwbuQln3EW8CZ0oD}
21 |
22 | # Runtime, Requests/Response and Beaver Metrics
23 | metrics:
24 | prometheus:
25 | # Route for the metrics endpoint
26 | endpoint: ${BEAVER_METRICS_PROM_ENDPOINT:-/metrics}
27 |
28 | # Application Database
29 | database:
30 | # Database driver
31 | driver: ${BEAVER_DB_DRIVER:-redis}
32 |
33 | # Redis Configs
34 | redis:
35 | # Redis address
36 | address: ${BEAVER_DB_REDIS_ADDR:-localhost:6379}
37 | # Redis password
38 | password: ${BEAVER_DB_REDIS_PASSWORD:- }
39 | # Redis database
40 | db: ${BEAVER_DB_REDIS_DB:-0}
41 |
42 | # Log configs
43 | log:
44 | # Log level, it can be debug, info, warn, error, panic, fatal
45 | level: ${BEAVER_LOG_LEVEL:-info}
46 | # Output can be stdout or abs path to log file /var/logs/beaver.log
47 | output: ${BEAVER_LOG_OUTPUT:-stdout}
48 | # Format can be json
49 | format: ${BEAVER_LOG_FORMAT:-json}
50 |
--------------------------------------------------------------------------------
/config.toml:
--------------------------------------------------------------------------------
1 | ignoreGeneratedHeader = false
2 | severity = "warning"
3 | confidence = 0.8
4 | errorCode = 0
5 | warningCode = 0
6 |
7 | [rule.blank-imports]
8 | [rule.context-as-argument]
9 | [rule.context-keys-type]
10 | [rule.dot-imports]
11 | [rule.error-return]
12 | [rule.error-strings]
13 | [rule.error-naming]
14 | [rule.exported]
15 | [rule.if-return]
16 | [rule.increment-decrement]
17 | [rule.var-naming]
18 | [rule.var-declaration]
19 | [rule.package-comments]
20 | [rule.range]
21 | [rule.receiver-naming]
22 | [rule.time-naming]
23 | [rule.unexported-return]
24 | [rule.indent-error-flow]
25 | [rule.errorf]
26 | [rule.empty-block]
27 | [rule.superfluous-else]
28 | [rule.unused-parameter]
29 | [rule.unreachable-code]
30 | [rule.redefines-builtin-id]
--------------------------------------------------------------------------------
/core/api/channel.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package api
6 |
7 | import (
8 | "encoding/json"
9 | "fmt"
10 |
11 | "github.com/clivern/beaver/core/driver"
12 | "github.com/clivern/beaver/core/util"
13 |
14 | "github.com/go-redis/redis"
15 | log "github.com/sirupsen/logrus"
16 | )
17 |
18 | // ChannelsHashPrefix is the hash prefix
19 | const ChannelsHashPrefix string = "beaver.channel"
20 |
21 | // Channel struct
22 | type Channel struct {
23 | Driver *driver.Redis
24 | }
25 |
26 | // ChannelResult struct
27 | type ChannelResult struct {
28 | Name string `json:"name"`
29 | Type string `json:"type"`
30 | CreatedAt int64 `json:"created_at"`
31 | UpdatedAt int64 `json:"updated_at"`
32 | }
33 |
34 | // LoadFromJSON load object from json
35 | func (c *ChannelResult) LoadFromJSON(data []byte) (bool, error) {
36 | err := json.Unmarshal(data, &c)
37 | if err != nil {
38 | return false, err
39 | }
40 | return true, nil
41 | }
42 |
43 | // ConvertToJSON converts object to json
44 | func (c *ChannelResult) ConvertToJSON() (string, error) {
45 | data, err := json.Marshal(&c)
46 | if err != nil {
47 | return "", err
48 | }
49 | return string(data), nil
50 | }
51 |
52 | // Init initialize the redis connection
53 | func (c *Channel) Init() bool {
54 | c.Driver = driver.NewRedisDriver()
55 |
56 | result, err := c.Driver.Connect()
57 |
58 | if !result {
59 | log.Errorf(
60 | `Error while connecting to redis: %s`,
61 | err.Error(),
62 | )
63 | return false
64 | }
65 |
66 | log.Infof(`Redis connection established`)
67 |
68 | return true
69 | }
70 |
71 | // CreateChannel creates a channel
72 | func (c *Channel) CreateChannel(channel ChannelResult) (bool, error) {
73 | exists, err := c.Driver.HExists(ChannelsHashPrefix, channel.Name)
74 |
75 | if err != nil {
76 | log.Errorf(
77 | `Error while creating channel %s: %s`,
78 | channel.Name,
79 | err.Error(),
80 | )
81 | return false, fmt.Errorf(
82 | `Error while creating channel %s`,
83 | channel.Name,
84 | )
85 | }
86 |
87 | if exists {
88 | log.Warningf(
89 | `Trying to create existent channel %s`,
90 | channel.Name,
91 | )
92 | return false, fmt.Errorf(
93 | `Trying to create existent channel %s`,
94 | channel.Name,
95 | )
96 | }
97 |
98 | result, err := channel.ConvertToJSON()
99 |
100 | if err != nil {
101 | log.Errorf(
102 | `Something wrong with channel %s data: %s`,
103 | channel.Name,
104 | err.Error(),
105 | )
106 | return false, fmt.Errorf(
107 | `Something wrong with channel %s data`,
108 | channel.Name,
109 | )
110 | }
111 |
112 | _, err = c.Driver.HSet(ChannelsHashPrefix, channel.Name, result)
113 |
114 | if err != nil {
115 | log.Errorf(
116 | `Error while creating channel %s: %s`,
117 | channel.Name,
118 | err.Error(),
119 | )
120 | return false, fmt.Errorf(
121 | `Error while creating channel %s`,
122 | channel.Name,
123 | )
124 | }
125 |
126 | log.Infof(
127 | `Channel %s with type %s got created`,
128 | channel.Name,
129 | channel.Type,
130 | )
131 |
132 | return true, nil
133 | }
134 |
135 | // GetChannelByName gets a channel by name
136 | func (c *Channel) GetChannelByName(name string) (ChannelResult, error) {
137 | var channelResult ChannelResult
138 |
139 | exists, err := c.Driver.HExists(ChannelsHashPrefix, name)
140 |
141 | if err != nil {
142 | log.Errorf(
143 | `Error while getting channel %s: %s`,
144 | name,
145 | err.Error(),
146 | )
147 | return channelResult, fmt.Errorf(
148 | `Error while getting channel %s`,
149 | name,
150 | )
151 | }
152 |
153 | if !exists {
154 | log.Warningf(
155 | `Trying to get non existent channel %s`,
156 | name,
157 | )
158 | return channelResult, fmt.Errorf(
159 | `Trying to get non existent channel %s`,
160 | name,
161 | )
162 | }
163 |
164 | value, err := c.Driver.HGet(ChannelsHashPrefix, name)
165 |
166 | if err != nil {
167 | log.Errorf(
168 | `Error while getting channel %s: %s`,
169 | name,
170 | err.Error(),
171 | )
172 | return channelResult, fmt.Errorf(
173 | `Error while getting channel %s`,
174 | name,
175 | )
176 | }
177 |
178 | _, err = channelResult.LoadFromJSON([]byte(value))
179 |
180 | if err != nil {
181 | log.Errorf(
182 | `Error while getting channel %s: %s`,
183 | name,
184 | err.Error(),
185 | )
186 | return channelResult, fmt.Errorf(
187 | `Error while getting channel %s`,
188 | name,
189 | )
190 | }
191 |
192 | return channelResult, nil
193 | }
194 |
195 | // UpdateChannelByName updates a channel by name
196 | func (c *Channel) UpdateChannelByName(channel ChannelResult) (bool, error) {
197 | exists, err := c.Driver.HExists(ChannelsHashPrefix, channel.Name)
198 |
199 | if err != nil {
200 | log.Errorf(
201 | `Error while updating channel %s: %s`,
202 | channel.Name,
203 | err.Error(),
204 | )
205 | return false, fmt.Errorf(
206 | `Error while updating channel %s`,
207 | channel.Name,
208 | )
209 | }
210 |
211 | if !exists {
212 | log.Warningf(
213 | `Trying to create non existent channel %s`,
214 | channel.Name,
215 | )
216 | return false, fmt.Errorf(
217 | `Trying to create non existent channel %s`,
218 | channel.Name,
219 | )
220 | }
221 |
222 | result, err := channel.ConvertToJSON()
223 |
224 | if err != nil {
225 | log.Errorf(
226 | `Something wrong with channel %s data: %s`,
227 | channel.Name,
228 | err.Error(),
229 | )
230 | return false, fmt.Errorf(
231 | `Something wrong with channel %s data`,
232 | channel.Name,
233 | )
234 | }
235 |
236 | _, err = c.Driver.HSet(ChannelsHashPrefix, channel.Name, result)
237 |
238 | if err != nil {
239 | log.Errorf(
240 | `Error while updating channel %s: %s`,
241 | channel.Name,
242 | err.Error(),
243 | )
244 | return false, fmt.Errorf(
245 | `Error while updating channel %s`,
246 | channel.Name,
247 | )
248 | }
249 |
250 | log.Infof(
251 | `Channel %s got updated to type %s`,
252 | channel.Name,
253 | channel.Type,
254 | )
255 |
256 | return true, nil
257 | }
258 |
259 | // DeleteChannelByName deletes a channel with name
260 | func (c *Channel) DeleteChannelByName(name string) (bool, error) {
261 | deleted, err := c.Driver.HDel(ChannelsHashPrefix, name)
262 |
263 | if err != nil {
264 | log.Errorf(
265 | `Error while deleting channel %s: %s`,
266 | name,
267 | err.Error(),
268 | )
269 | return false, fmt.Errorf(
270 | `Error while deleting channel %s`,
271 | name,
272 | )
273 | }
274 |
275 | if deleted <= 0 {
276 | log.Warningf(
277 | `Trying to delete non existent channel %s`,
278 | name,
279 | )
280 | return false, fmt.Errorf(
281 | `Trying to delete non existent channel %s`,
282 | name,
283 | )
284 | }
285 |
286 | c.Driver.HTruncate(fmt.Sprintf("%s.listeners", name))
287 | c.Driver.HTruncate(fmt.Sprintf("%s.subscribers", name))
288 |
289 | log.Infof(
290 | `Channel %s got deleted`,
291 | name,
292 | )
293 |
294 | return true, nil
295 | }
296 |
297 | // CountListeners counts channel listeners
298 | func (c *Channel) CountListeners(name string) int64 {
299 |
300 | count, err := c.Driver.HLen(fmt.Sprintf("%s.listeners", name))
301 |
302 | if err != nil {
303 | log.Errorf(
304 | `Error while counting %s listeners %s`,
305 | name,
306 | err.Error(),
307 | )
308 |
309 | return 0
310 | }
311 |
312 | return count
313 |
314 | }
315 |
316 | // CountSubscribers counts channel subscribers
317 | func (c *Channel) CountSubscribers(name string) int64 {
318 |
319 | count, err := c.Driver.HLen(fmt.Sprintf("%s.subscribers", name))
320 |
321 | if err != nil {
322 | log.Errorf(
323 | `Error while counting %s subscribers %s`,
324 | name,
325 | err.Error(),
326 | )
327 | return 0
328 | }
329 |
330 | return count
331 | }
332 |
333 | // ChannelsExist checks if channels exist
334 | func (c *Channel) ChannelsExist(channels []string) (bool, error) {
335 | for _, channel := range channels {
336 | exists, err := c.Driver.HExists(ChannelsHashPrefix, channel)
337 |
338 | if err != nil {
339 | log.Errorf(
340 | `Error while getting channel %s: %s`,
341 | channel,
342 | err.Error(),
343 | )
344 |
345 | return false, fmt.Errorf(
346 | `Error while getting channel %s`,
347 | channel,
348 | )
349 | }
350 |
351 | if !exists {
352 | log.Infof(
353 | `Channel %s not exist`,
354 | channel,
355 | )
356 |
357 | return false, fmt.Errorf(
358 | `Channel %s not exist`,
359 | channel,
360 | )
361 | }
362 | }
363 |
364 | return true, nil
365 | }
366 |
367 | // ChannelExist checks if channel exists
368 | func (c *Channel) ChannelExist(channel string) (bool, error) {
369 | return c.ChannelsExist([]string{channel})
370 | }
371 |
372 | // ChannelScan get clients under channel listeners (connected clients)
373 | func (c *Channel) ChannelScan(channel string) *redis.ScanCmd {
374 | return c.Driver.HScan(fmt.Sprintf("%s.listeners", channel), 0, "", 0)
375 | }
376 |
377 | // GetListeners gets a list of listeners with channel name
378 | func (c *Channel) GetListeners(channel string) []string {
379 | var result []string
380 | var key string
381 | validate := util.Validator{}
382 |
383 | iter := c.Driver.HScan(fmt.Sprintf("%s.listeners", channel), 0, "", 0).Iterator()
384 |
385 | for iter.Next() {
386 | key = iter.Val()
387 | if key != "" && validate.IsUUID4(key) {
388 | result = append(result, key)
389 | }
390 | }
391 |
392 | return result
393 | }
394 |
395 | // GetSubscribers gets a list of subscribers with channel name
396 | func (c *Channel) GetSubscribers(channel string) []string {
397 | var result []string
398 | var key string
399 | validate := util.Validator{}
400 |
401 | iter := c.Driver.HScan(fmt.Sprintf("%s.subscribers", channel), 0, "", 0).Iterator()
402 |
403 | for iter.Next() {
404 | key = iter.Val()
405 | if key != "" && validate.IsUUID4(key) {
406 | result = append(result, key)
407 | }
408 | }
409 |
410 | return result
411 | }
412 |
--------------------------------------------------------------------------------
/core/api/client.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package api
6 |
7 | import (
8 | "encoding/json"
9 | "fmt"
10 | "time"
11 |
12 | "github.com/clivern/beaver/core/driver"
13 | "github.com/clivern/beaver/core/util"
14 |
15 | log "github.com/sirupsen/logrus"
16 | "github.com/spf13/viper"
17 | )
18 |
19 | // ClientsHashPrefix is the hash prefix
20 | const ClientsHashPrefix string = "beaver.client"
21 |
22 | // Client struct
23 | type Client struct {
24 | Driver *driver.Redis
25 | }
26 |
27 | // ClientResult struct
28 | type ClientResult struct {
29 | ID string `json:"id"`
30 | Token string `json:"token"`
31 | Channels []string `json:"channels"`
32 | CreatedAt int64 `json:"created_at"`
33 | UpdatedAt int64 `json:"updated_at"`
34 | }
35 |
36 | // LoadFromJSON load object from json
37 | func (c *ClientResult) LoadFromJSON(data []byte) (bool, error) {
38 | err := json.Unmarshal(data, &c)
39 | if err != nil {
40 | return false, err
41 | }
42 | return true, nil
43 | }
44 |
45 | // ConvertToJSON converts object to json
46 | func (c *ClientResult) ConvertToJSON() (string, error) {
47 | data, err := json.Marshal(&c)
48 | if err != nil {
49 | return "", err
50 | }
51 | return string(data), nil
52 | }
53 |
54 | // GenerateClient generates client ID & Token
55 | func (c *ClientResult) GenerateClient() (bool, error) {
56 |
57 | now := time.Now().Unix()
58 | c.ID = util.GenerateUUID4()
59 |
60 | token, err := util.GenerateJWTToken(
61 | fmt.Sprintf("%s@%d", c.ID, now),
62 | now,
63 | viper.GetString("app.secret"),
64 | )
65 |
66 | if err != nil {
67 | return false, err
68 | }
69 |
70 | c.Token = token
71 | c.CreatedAt = now
72 | c.UpdatedAt = now
73 |
74 | return true, nil
75 | }
76 |
77 | // Init initialize the redis connection
78 | func (c *Client) Init() bool {
79 | c.Driver = driver.NewRedisDriver()
80 |
81 | result, err := c.Driver.Connect()
82 | if !result {
83 | log.Errorf(
84 | `Error while connecting to redis: %s`,
85 | err.Error(),
86 | )
87 | return false
88 | }
89 |
90 | log.Infof(`Redis connection established`)
91 |
92 | return true
93 | }
94 |
95 | // CreateClient creates a client
96 | func (c *Client) CreateClient(client ClientResult) (bool, error) {
97 |
98 | exists, err := c.Driver.HExists(ClientsHashPrefix, client.ID)
99 |
100 | if err != nil {
101 | log.Errorf(
102 | `Error while creating client %s: %s`,
103 | client.ID,
104 | err.Error(),
105 | )
106 |
107 | return false, fmt.Errorf(
108 | `Error while creating client %s`,
109 | client.ID,
110 | )
111 | }
112 |
113 | if exists {
114 | log.Warningf(
115 | `Trying to create existent client %s`,
116 | client.ID,
117 | )
118 |
119 | return false, fmt.Errorf(
120 | `Trying to create existent client %s`,
121 | client.ID,
122 | )
123 | }
124 |
125 | result, err := client.ConvertToJSON()
126 |
127 | if err != nil {
128 | log.Errorf(
129 | `Something wrong with client %s data: %s`,
130 | client.ID,
131 | err.Error(),
132 | )
133 | return false, fmt.Errorf(
134 | `Something wrong with client %s data`,
135 | client.ID,
136 | )
137 | }
138 |
139 | _, err = c.Driver.HSet(ClientsHashPrefix, client.ID, result)
140 |
141 | if err != nil {
142 | log.Errorf(
143 | `Error while creating client %s: %s`,
144 | client.ID,
145 | err.Error(),
146 | )
147 | return false, fmt.Errorf(
148 | `Error while creating client %s`,
149 | client.ID,
150 | )
151 | }
152 |
153 | log.Infof(
154 | `Client %s got created`,
155 | client.ID,
156 | )
157 |
158 | for _, channel := range client.Channels {
159 | ok, err := c.AddToChannel(client.ID, channel)
160 | if !ok || err != nil {
161 | return false, err
162 | }
163 | }
164 |
165 | return true, nil
166 | }
167 |
168 | // GetClientByID gets a client by ID
169 | func (c *Client) GetClientByID(ID string) (ClientResult, error) {
170 |
171 | var clientResult ClientResult
172 |
173 | exists, err := c.Driver.HExists(ClientsHashPrefix, ID)
174 |
175 | if err != nil {
176 | log.Errorf(
177 | `Error while getting client %s: %s`,
178 | ID,
179 | err.Error(),
180 | )
181 | return clientResult, fmt.Errorf(
182 | "Error while getting client %s",
183 | ID,
184 | )
185 | }
186 |
187 | if !exists {
188 | log.Warningf(
189 | `Trying to get non existent client %s`,
190 | ID,
191 | )
192 | return clientResult, fmt.Errorf(
193 | "Trying to get non existent client %s",
194 | ID,
195 | )
196 | }
197 |
198 | value, err := c.Driver.HGet(ClientsHashPrefix, ID)
199 |
200 | if err != nil {
201 | log.Errorf(
202 | `Error while getting client %s: %s`,
203 | ID,
204 | err.Error(),
205 | )
206 | return clientResult, fmt.Errorf(
207 | "Error while getting client %s",
208 | ID,
209 | )
210 | }
211 |
212 | _, err = clientResult.LoadFromJSON([]byte(value))
213 |
214 | if err != nil {
215 | log.Errorf(
216 | `Error while getting client %s: %s`,
217 | ID,
218 | err.Error(),
219 | )
220 | return clientResult, fmt.Errorf(
221 | "Error while getting client %s",
222 | ID,
223 | )
224 | }
225 |
226 | return clientResult, nil
227 | }
228 |
229 | // UpdateClientByID updates a client by ID
230 | func (c *Client) UpdateClientByID(client ClientResult) (bool, error) {
231 |
232 | exists, err := c.Driver.HExists(ClientsHashPrefix, client.ID)
233 |
234 | if err != nil {
235 | log.Errorf(
236 | `Error while updating client %s: %s`,
237 | client.ID,
238 | err.Error(),
239 | )
240 | return false, fmt.Errorf(
241 | `Error while updating client %s`,
242 | client.ID,
243 | )
244 | }
245 |
246 | if !exists {
247 | log.Warningf(
248 | `Trying to create non existent client %s`,
249 | client.ID,
250 | )
251 | return false, fmt.Errorf(
252 | `Trying to create non existent client %s`,
253 | client.ID,
254 | )
255 | }
256 |
257 | result, err := client.ConvertToJSON()
258 |
259 | if err != nil {
260 | log.Errorf(
261 | `Something wrong with client %s data: %s`,
262 | client.ID,
263 | err.Error(),
264 | )
265 | return false, fmt.Errorf(
266 | `Something wrong with client %s data`,
267 | client.ID,
268 | )
269 | }
270 |
271 | _, err = c.Driver.HSet(ClientsHashPrefix, client.ID, result)
272 |
273 | if err != nil {
274 | log.Errorf(
275 | `Error while updating client %s: %s`,
276 | client.ID,
277 | err.Error(),
278 | )
279 | return false, fmt.Errorf(
280 | `Error while updating client %s`,
281 | client.ID,
282 | )
283 | }
284 |
285 | log.Infof(
286 | `Client %s got updated`,
287 | client.ID,
288 | )
289 |
290 | return true, nil
291 | }
292 |
293 | // DeleteClientByID deletes a client with ID
294 | func (c *Client) DeleteClientByID(ID string) (bool, error) {
295 |
296 | client, err := c.GetClientByID(ID)
297 |
298 | if err != nil {
299 | log.Errorf(
300 | `Error while deleting client %s: %s`,
301 | ID,
302 | err.Error(),
303 | )
304 | return false, fmt.Errorf(
305 | `Error while deleting client %s`,
306 | ID,
307 | )
308 | }
309 |
310 | for _, channel := range client.Channels {
311 | ok, err := c.RemoveFromChannel(ID, channel)
312 | if !ok || err != nil {
313 | return false, err
314 | }
315 | }
316 |
317 | // Remove client from clients
318 | _, err = c.Driver.HDel(ClientsHashPrefix, ID)
319 |
320 | if err != nil {
321 | log.Errorf(
322 | `Error while deleting client %s: %s`,
323 | ID,
324 | err.Error(),
325 | )
326 | return false, fmt.Errorf(
327 | `Error while deleting client %s`,
328 | ID,
329 | )
330 | }
331 |
332 | log.Infof(
333 | `Client %s got deleted`,
334 | ID,
335 | )
336 |
337 | return true, nil
338 | }
339 |
340 | // Unsubscribe from channels
341 | func (c *Client) Unsubscribe(ID string, channels []string) (bool, error) {
342 |
343 | validator := util.Validator{}
344 | clientResult, err := c.GetClientByID(ID)
345 |
346 | if err != nil {
347 | return false, err
348 | }
349 |
350 | for i, channel := range clientResult.Channels {
351 | if validator.IsIn(channel, channels) {
352 | ok, err := c.RemoveFromChannel(ID, channel)
353 | if !ok || err != nil {
354 | return false, err
355 | }
356 | clientResult.Channels = util.Unset(clientResult.Channels, i)
357 | }
358 | }
359 |
360 | return c.UpdateClientByID(clientResult)
361 | }
362 |
363 | // Subscribe to channels
364 | func (c *Client) Subscribe(ID string, channels []string) (bool, error) {
365 |
366 | validator := util.Validator{}
367 | clientResult, err := c.GetClientByID(ID)
368 |
369 | if err != nil {
370 | return false, err
371 | }
372 |
373 | for _, channel := range channels {
374 | if !validator.IsIn(channel, clientResult.Channels) {
375 | ok, err := c.AddToChannel(ID, channel)
376 | if !ok || err != nil {
377 | return false, err
378 | }
379 | clientResult.Channels = append(clientResult.Channels, channel)
380 | }
381 | }
382 |
383 | return c.UpdateClientByID(clientResult)
384 | }
385 |
386 | // AddToChannel adds a client to a channel
387 | func (c *Client) AddToChannel(ID string, channel string) (bool, error) {
388 | // Remove client from channel subscribers
389 | _, err := c.Driver.HSet(fmt.Sprintf("%s.subscribers", channel), ID, "")
390 |
391 | if err != nil {
392 | log.Errorf(
393 | `Error while adding client %s to channel %s: %s`,
394 | ID,
395 | fmt.Sprintf("%s.subscribers", channel),
396 | err.Error(),
397 | )
398 | return false, fmt.Errorf(
399 | `Error while adding client %s to channel %s`,
400 | ID,
401 | fmt.Sprintf("%s.subscribers", channel),
402 | )
403 | }
404 |
405 | log.Infof(
406 | `Client %s added to channel %s`,
407 | ID,
408 | channel,
409 | )
410 |
411 | return true, nil
412 | }
413 |
414 | // RemoveFromChannel removes a client from a channel
415 | func (c *Client) RemoveFromChannel(ID string, channel string) (bool, error) {
416 | // Remove client from channel subscribers
417 | _, err := c.Driver.HDel(fmt.Sprintf("%s.subscribers", channel), ID)
418 |
419 | if err != nil {
420 | log.Errorf(
421 | `Error while removing client %s from channel %s: %s`,
422 | ID,
423 | fmt.Sprintf("%s.subscribers", channel),
424 | err.Error(),
425 | )
426 | return false, fmt.Errorf(
427 | `Error while removing client %s from %s`,
428 | ID,
429 | fmt.Sprintf("%s.subscribers", channel),
430 | )
431 | }
432 |
433 | // Delete from listeners if stale
434 | _, err = c.Driver.HDel(fmt.Sprintf("%s.listeners", channel), ID)
435 |
436 | if err != nil {
437 | log.Errorf(
438 | `Error while removing client %s from channel %s: %s`,
439 | ID,
440 | fmt.Sprintf("%s.listeners", channel),
441 | err.Error(),
442 | )
443 | return false, fmt.Errorf(
444 | `Error while removing client %s from %s`,
445 | ID,
446 | fmt.Sprintf("%s.listeners", channel),
447 | )
448 | }
449 |
450 | log.Infof(
451 | `Client %s removed from channel %s`,
452 | ID,
453 | channel,
454 | )
455 |
456 | return true, nil
457 | }
458 |
459 | // Connect a client
460 | func (c *Client) Connect(clientResult ClientResult) (bool, error) {
461 | for _, channel := range clientResult.Channels {
462 | // Remove client from channel listeners
463 | _, err := c.Driver.HSet(fmt.Sprintf("%s.listeners", channel), clientResult.ID, "")
464 |
465 | if err != nil {
466 | log.Errorf(
467 | `Error while adding client %s to channel %s: %s`,
468 | clientResult.ID,
469 | fmt.Sprintf("%s.listeners", channel),
470 | err.Error(),
471 | )
472 | return false, fmt.Errorf(
473 | `Error while adding client %s to channel %s`,
474 | clientResult.ID,
475 | fmt.Sprintf("%s.listeners", channel),
476 | )
477 | }
478 | }
479 |
480 | log.Infof(
481 | `Client %s connected to all subscribed channels`,
482 | clientResult.ID,
483 | )
484 |
485 | return true, nil
486 | }
487 |
488 | // Disconnect a client
489 | func (c *Client) Disconnect(clientResult ClientResult) (bool, error) {
490 | for _, channel := range clientResult.Channels {
491 | // Remove client from channel listeners
492 | _, err := c.Driver.HDel(fmt.Sprintf("%s.listeners", channel), clientResult.ID)
493 |
494 | if err != nil {
495 | log.Errorf(
496 | `Error while removing client %s from channel %s: %s`,
497 | clientResult.ID,
498 | fmt.Sprintf("%s.listeners", channel),
499 | err.Error(),
500 | )
501 | return false, fmt.Errorf(
502 | "Error while removing client %s from %s",
503 | clientResult.ID,
504 | fmt.Sprintf("%s.listeners", channel),
505 | )
506 | }
507 | }
508 |
509 | log.Infof(
510 | `Client %s disconnected from all subscribed channels`,
511 | clientResult.ID,
512 | )
513 |
514 | return true, nil
515 | }
516 |
--------------------------------------------------------------------------------
/core/api/config.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package api
6 |
7 | import (
8 | "encoding/json"
9 | "fmt"
10 |
11 | "github.com/clivern/beaver/core/driver"
12 |
13 | log "github.com/sirupsen/logrus"
14 | )
15 |
16 | // ConfigsHashPrefix is the hash prefix
17 | const ConfigsHashPrefix string = "beaver.config"
18 |
19 | // Config struct
20 | type Config struct {
21 | Driver *driver.Redis
22 | }
23 |
24 | // ConfigResult struct
25 | type ConfigResult struct {
26 | Key string `json:"key"`
27 | Value string `json:"value"`
28 | }
29 |
30 | // LoadFromJSON load object from json
31 | func (c *ConfigResult) LoadFromJSON(data []byte) (bool, error) {
32 | err := json.Unmarshal(data, &c)
33 |
34 | if err != nil {
35 | return false, err
36 | }
37 |
38 | return true, nil
39 | }
40 |
41 | // ConvertToJSON converts object to json
42 | func (c *ConfigResult) ConvertToJSON() (string, error) {
43 | data, err := json.Marshal(&c)
44 |
45 | if err != nil {
46 | return "", err
47 | }
48 |
49 | return string(data), nil
50 | }
51 |
52 | // Init initialize the redis connection
53 | func (c *Config) Init() bool {
54 | c.Driver = driver.NewRedisDriver()
55 |
56 | result, err := c.Driver.Connect()
57 |
58 | if !result {
59 | log.Errorf(
60 | `Error while connecting to redis: %s`,
61 | err.Error(),
62 | )
63 | return false
64 | }
65 |
66 | log.Infof(`Redis connection established`)
67 |
68 | return true
69 | }
70 |
71 | // CreateConfig creates a config
72 | func (c *Config) CreateConfig(key string, value string) (bool, error) {
73 | exists, err := c.Driver.HExists(ConfigsHashPrefix, key)
74 |
75 | if err != nil {
76 | log.Errorf(
77 | `Error while creating config %s: %s`,
78 | key,
79 | err.Error(),
80 | )
81 |
82 | return false, fmt.Errorf(
83 | `Error while creating config %s`,
84 | key,
85 | )
86 | }
87 |
88 | if exists {
89 | log.Warningf(
90 | `Trying to create existent config %s`,
91 | key,
92 | )
93 |
94 | return false, fmt.Errorf(
95 | `Trying to create existent config %s`,
96 | key,
97 | )
98 | }
99 |
100 | _, err = c.Driver.HSet(ConfigsHashPrefix, key, value)
101 |
102 | if err != nil {
103 | log.Errorf(
104 | `Error while creating config %s: %s`,
105 | key,
106 | err.Error(),
107 | )
108 |
109 | return false, fmt.Errorf(
110 | `Error while creating config %s`,
111 | key,
112 | )
113 | }
114 |
115 | log.Infof(
116 | `Config %s got created`,
117 | key,
118 | )
119 |
120 | return true, nil
121 | }
122 |
123 | // GetConfigByKey gets a config value with key
124 | func (c *Config) GetConfigByKey(key string) (string, error) {
125 |
126 | exists, err := c.Driver.HExists(ConfigsHashPrefix, key)
127 |
128 | if err != nil {
129 | log.Errorf(
130 | `Error while getting config %s: %s`,
131 | key,
132 | err.Error(),
133 | )
134 |
135 | return "", fmt.Errorf(
136 | `Error while getting config %s`,
137 | key,
138 | )
139 | }
140 |
141 | if !exists {
142 | log.Warningf(
143 | `Trying to get non existent config %s`,
144 | key,
145 | )
146 |
147 | return "", fmt.Errorf(
148 | `Trying to get non existent config %s`,
149 | key,
150 | )
151 | }
152 |
153 | value, err := c.Driver.HGet(ConfigsHashPrefix, key)
154 |
155 | if err != nil {
156 | log.Errorf(
157 | `Error while getting config %s: %s`,
158 | key,
159 | err.Error(),
160 | )
161 |
162 | return "", fmt.Errorf(
163 | `Error while getting config %s`,
164 | key,
165 | )
166 | }
167 |
168 | return value, nil
169 | }
170 |
171 | // UpdateConfigByKey updates a config with key
172 | func (c *Config) UpdateConfigByKey(key string, value string) (bool, error) {
173 |
174 | exists, err := c.Driver.HExists(ConfigsHashPrefix, key)
175 |
176 | if err != nil {
177 | log.Errorf(
178 | `Error while updating config %s: %s`,
179 | key,
180 | err.Error(),
181 | )
182 |
183 | return false, fmt.Errorf(
184 | `Error while updating config %s`,
185 | key,
186 | )
187 | }
188 |
189 | if !exists {
190 | log.Warningf(
191 | `Trying to update non existent config %s`,
192 | key,
193 | )
194 |
195 | return false, fmt.Errorf(
196 | `Trying to update non existent config %s`,
197 | key,
198 | )
199 | }
200 |
201 | _, err = c.Driver.HSet(ConfigsHashPrefix, key, value)
202 |
203 | if err != nil {
204 | log.Errorf(
205 | `Error while updating config %s: %s`,
206 | key,
207 | err.Error(),
208 | )
209 |
210 | return false, fmt.Errorf(
211 | `Error while updating config %s`,
212 | key,
213 | )
214 | }
215 |
216 | log.Infof(
217 | `Config %s got updated`,
218 | key,
219 | )
220 |
221 | return true, nil
222 |
223 | }
224 |
225 | // DeleteConfigByKey deletes a config with key
226 | func (c *Config) DeleteConfigByKey(key string) (bool, error) {
227 |
228 | deleted, err := c.Driver.HDel(ConfigsHashPrefix, key)
229 |
230 | if err != nil {
231 | log.Errorf(
232 | `Error while deleting config %s: %s`,
233 | key,
234 | err.Error(),
235 | )
236 |
237 | return false, fmt.Errorf(
238 | `Error while deleting config %s`,
239 | key,
240 | )
241 | }
242 |
243 | if deleted <= 0 {
244 | log.Warningf(
245 | `Trying to delete non existent config %s`,
246 | key,
247 | )
248 |
249 | return false, fmt.Errorf(
250 | `Trying to delete non existent config %s`,
251 | key,
252 | )
253 | }
254 |
255 | log.Infof(
256 | `Config %s got deleted`,
257 | key,
258 | )
259 |
260 | return true, nil
261 | }
262 |
--------------------------------------------------------------------------------
/core/controller/channel.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package controller
6 |
7 | import (
8 | "net/http"
9 | "time"
10 |
11 | "github.com/clivern/beaver/core/api"
12 | "github.com/clivern/beaver/core/util"
13 |
14 | "github.com/gin-gonic/gin"
15 | )
16 |
17 | // GetChannelByName controller
18 | func GetChannelByName(c *gin.Context) {
19 |
20 | var channelResult api.ChannelResult
21 | validate := util.Validator{}
22 |
23 | name := c.Param("name")
24 |
25 | if validate.IsEmpty(name) || !validate.IsSlug(name, 3, 60) {
26 | c.JSON(http.StatusBadRequest, gin.H{
27 | "status": "error",
28 | "error": "Channel name must be alphanumeric with length from 3 to 60",
29 | })
30 | return
31 | }
32 |
33 | channel := api.Channel{}
34 |
35 | if !channel.Init() {
36 | c.JSON(http.StatusServiceUnavailable, gin.H{
37 | "status": "error",
38 | "error": "Internal server error",
39 | })
40 | return
41 | }
42 |
43 | channelResult, err := channel.GetChannelByName(name)
44 |
45 | if err != nil {
46 | c.JSON(http.StatusNotFound, gin.H{
47 | "status": "error",
48 | "error": err.Error(),
49 | })
50 | return
51 | }
52 |
53 | if channelResult.Type == "presence" {
54 | c.JSON(http.StatusOK, gin.H{
55 | "name": channelResult.Name,
56 | "type": channelResult.Type,
57 | "subscribers_count": channel.CountSubscribers(name),
58 | "listeners_count": channel.CountListeners(name),
59 | "subscribers": channel.GetSubscribers(name),
60 | "listeners": channel.GetListeners(name),
61 | "created_at": channelResult.CreatedAt,
62 | "updated_at": channelResult.UpdatedAt,
63 | })
64 | return
65 | }
66 |
67 | c.JSON(http.StatusOK, gin.H{
68 | "name": channelResult.Name,
69 | "type": channelResult.Type,
70 | "subscribers_count": channel.CountSubscribers(name),
71 | "listeners_count": channel.CountListeners(name),
72 | "created_at": channelResult.CreatedAt,
73 | "updated_at": channelResult.UpdatedAt,
74 | })
75 | }
76 |
77 | // CreateChannel controller
78 | func CreateChannel(c *gin.Context) {
79 |
80 | var channelResult api.ChannelResult
81 | validate := util.Validator{}
82 |
83 | channel := api.Channel{}
84 |
85 | rawBody, err := c.GetRawData()
86 |
87 | if err != nil {
88 | c.JSON(http.StatusBadRequest, gin.H{
89 | "status": "error",
90 | "error": "Invalid request",
91 | })
92 | return
93 | }
94 |
95 | ok, err := channelResult.LoadFromJSON(rawBody)
96 |
97 | if !ok || err != nil {
98 | c.JSON(http.StatusBadRequest, gin.H{
99 | "status": "error",
100 | "error": "Invalid request",
101 | })
102 | return
103 | }
104 |
105 | if validate.IsEmpty(channelResult.Name) || !validate.IsSlug(channelResult.Name, 3, 60) {
106 | c.JSON(http.StatusBadRequest, gin.H{
107 | "status": "error",
108 | "error": "Channel name must be alphanumeric with length from 3 to 60",
109 | })
110 | return
111 | }
112 |
113 | if !validate.IsIn(channelResult.Type, []string{"public", "private", "presence"}) {
114 | c.JSON(http.StatusBadRequest, gin.H{
115 | "status": "error",
116 | "error": "Channel type must be public, private or presence",
117 | })
118 | return
119 | }
120 |
121 | if !channel.Init() {
122 | c.JSON(http.StatusServiceUnavailable, gin.H{
123 | "status": "error",
124 | "error": "Internal server error",
125 | })
126 | return
127 | }
128 |
129 | channelResult.CreatedAt = time.Now().Unix()
130 | channelResult.UpdatedAt = time.Now().Unix()
131 |
132 | ok, err = channel.CreateChannel(channelResult)
133 |
134 | if !ok || err != nil {
135 | c.JSON(http.StatusBadRequest, gin.H{
136 | "status": "error",
137 | "error": err.Error(),
138 | })
139 | return
140 | }
141 |
142 | c.Status(http.StatusCreated)
143 | }
144 |
145 | // DeleteChannelByName controller
146 | func DeleteChannelByName(c *gin.Context) {
147 |
148 | validate := util.Validator{}
149 |
150 | name := c.Param("name")
151 |
152 | if validate.IsEmpty(name) || !validate.IsSlug(name, 3, 60) {
153 | c.JSON(http.StatusBadRequest, gin.H{
154 | "status": "error",
155 | "error": "Channel name must be alphanumeric with length from 3 to 60",
156 | })
157 | return
158 | }
159 |
160 | channel := api.Channel{}
161 |
162 | if !channel.Init() {
163 | c.JSON(http.StatusServiceUnavailable, gin.H{
164 | "status": "error",
165 | "error": "Internal server error",
166 | })
167 | return
168 | }
169 |
170 | _, err := channel.DeleteChannelByName(name)
171 |
172 | if err != nil {
173 | c.JSON(http.StatusNotFound, gin.H{
174 | "status": "error",
175 | "error": err.Error(),
176 | })
177 | return
178 | }
179 |
180 | c.Status(http.StatusNoContent)
181 | }
182 |
183 | // UpdateChannelByName controller
184 | func UpdateChannelByName(c *gin.Context) {
185 |
186 | var channelResult api.ChannelResult
187 | var currentChannelResult api.ChannelResult
188 | validate := util.Validator{}
189 |
190 | channel := api.Channel{}
191 |
192 | rawBody, err := c.GetRawData()
193 |
194 | if err != nil {
195 | c.JSON(http.StatusBadRequest, gin.H{
196 | "status": "error",
197 | "error": "Invalid request",
198 | })
199 | return
200 | }
201 |
202 | channelResult.LoadFromJSON(rawBody)
203 | channelResult.Name = c.Param("name")
204 |
205 | if validate.IsEmpty(channelResult.Name) || !validate.IsSlug(channelResult.Name, 3, 60) {
206 | c.JSON(http.StatusBadRequest, gin.H{
207 | "status": "error",
208 | "error": "Channel name must be alphanumeric with length from 3 to 60",
209 | })
210 | return
211 | }
212 |
213 | if !validate.IsIn(channelResult.Type, []string{"public", "private", "presence"}) {
214 | c.JSON(http.StatusBadRequest, gin.H{
215 | "status": "error",
216 | "error": "Channel type must be public, private or presence",
217 | })
218 | return
219 | }
220 |
221 | if !channel.Init() {
222 | c.JSON(http.StatusServiceUnavailable, gin.H{
223 | "status": "error",
224 | "error": "Internal server error",
225 | })
226 | return
227 | }
228 |
229 | currentChannelResult, err = channel.GetChannelByName(channelResult.Name)
230 |
231 | if err != nil {
232 | c.JSON(http.StatusNotFound, gin.H{
233 | "status": "error",
234 | "error": err.Error(),
235 | })
236 | return
237 | }
238 |
239 | // Update type & updated_at
240 | currentChannelResult.Type = channelResult.Type
241 | currentChannelResult.UpdatedAt = time.Now().Unix()
242 |
243 | ok, err := channel.UpdateChannelByName(currentChannelResult)
244 |
245 | if !ok || err != nil {
246 | c.JSON(http.StatusBadRequest, gin.H{
247 | "status": "error",
248 | "error": err.Error(),
249 | })
250 | return
251 | }
252 |
253 | c.Status(http.StatusOK)
254 | }
255 |
--------------------------------------------------------------------------------
/core/controller/client.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package controller
6 |
7 | import (
8 | "net/http"
9 |
10 | "github.com/clivern/beaver/core/api"
11 | "github.com/clivern/beaver/core/util"
12 |
13 | "github.com/gin-gonic/gin"
14 | )
15 |
16 | // GetClientByID controller
17 | func GetClientByID(c *gin.Context) {
18 |
19 | var clientResult api.ClientResult
20 | validate := util.Validator{}
21 |
22 | ID := c.Param("id")
23 |
24 | if validate.IsEmpty(ID) || !validate.IsUUID4(ID) {
25 | c.JSON(http.StatusBadRequest, gin.H{
26 | "status": "error",
27 | "error": "Client ID is invalid.",
28 | })
29 | return
30 | }
31 |
32 | client := api.Client{}
33 |
34 | if !client.Init() {
35 | c.JSON(http.StatusServiceUnavailable, gin.H{
36 | "status": "error",
37 | "error": "Internal server error",
38 | })
39 | return
40 | }
41 |
42 | clientResult, err := client.GetClientByID(ID)
43 |
44 | if err != nil {
45 | c.JSON(http.StatusNotFound, gin.H{
46 | "status": "error",
47 | "error": err.Error(),
48 | })
49 | return
50 | }
51 |
52 | c.JSON(http.StatusOK, gin.H{
53 | "id": clientResult.ID,
54 | "token": clientResult.Token,
55 | "channels": clientResult.Channels,
56 | "created_at": clientResult.CreatedAt,
57 | "updated_at": clientResult.UpdatedAt,
58 | })
59 | }
60 |
61 | // CreateClient controller
62 | func CreateClient(c *gin.Context) {
63 |
64 | var clientResult api.ClientResult
65 | validate := util.Validator{}
66 |
67 | client := api.Client{}
68 | channel := api.Channel{}
69 |
70 | rawBody, err := c.GetRawData()
71 |
72 | if err != nil {
73 | c.JSON(http.StatusBadRequest, gin.H{
74 | "status": "error",
75 | "error": "Invalid request",
76 | })
77 | return
78 | }
79 |
80 | ok, err := clientResult.LoadFromJSON(rawBody)
81 |
82 | if !ok || err != nil {
83 | c.JSON(http.StatusBadRequest, gin.H{
84 | "status": "error",
85 | "error": "Invalid request",
86 | })
87 | return
88 | }
89 |
90 | if !validate.IsSlugs(clientResult.Channels, 3, 60) {
91 | c.JSON(http.StatusBadRequest, gin.H{
92 | "status": "error",
93 | "error": "Provided client channels are invalid.",
94 | })
95 | return
96 | }
97 |
98 | if !client.Init() || !channel.Init() {
99 | c.JSON(http.StatusServiceUnavailable, gin.H{
100 | "status": "error",
101 | "error": "Internal server error",
102 | })
103 | return
104 | }
105 |
106 | ok, err = channel.ChannelsExist(clientResult.Channels)
107 |
108 | if !ok || err != nil {
109 | c.JSON(http.StatusBadRequest, gin.H{
110 | "status": "error",
111 | "error": err.Error(),
112 | })
113 | return
114 | }
115 |
116 | ok, err = clientResult.GenerateClient()
117 |
118 | if !ok || err != nil {
119 | c.JSON(http.StatusBadRequest, gin.H{
120 | "status": "error",
121 | "error": err.Error(),
122 | })
123 | return
124 | }
125 |
126 | ok, err = client.CreateClient(clientResult)
127 |
128 | if !ok || err != nil {
129 | c.JSON(http.StatusBadRequest, gin.H{
130 | "status": "error",
131 | "error": err.Error(),
132 | })
133 | return
134 | }
135 |
136 | c.JSON(http.StatusCreated, gin.H{
137 | "id": clientResult.ID,
138 | "token": clientResult.Token,
139 | "channels": clientResult.Channels,
140 | "created_at": clientResult.CreatedAt,
141 | "updated_at": clientResult.UpdatedAt,
142 | })
143 | }
144 |
145 | // DeleteClientByID controller
146 | func DeleteClientByID(c *gin.Context) {
147 |
148 | validate := util.Validator{}
149 | ID := c.Param("id")
150 |
151 | if validate.IsEmpty(ID) || !validate.IsUUID4(ID) {
152 | c.JSON(http.StatusBadRequest, gin.H{
153 | "status": "error",
154 | "error": "Client ID is invalid.",
155 | })
156 | return
157 | }
158 |
159 | client := api.Client{}
160 |
161 | if !client.Init() {
162 | c.JSON(http.StatusServiceUnavailable, gin.H{
163 | "status": "error",
164 | "error": "Internal server error",
165 | })
166 | return
167 | }
168 |
169 | _, err := client.DeleteClientByID(ID)
170 |
171 | if err != nil {
172 | c.JSON(http.StatusNotFound, gin.H{
173 | "status": "error",
174 | "error": err.Error(),
175 | })
176 | return
177 | }
178 |
179 | c.Status(http.StatusNoContent)
180 | }
181 |
182 | // Unsubscribe controller
183 | func Unsubscribe(c *gin.Context) {
184 |
185 | var clientResult api.ClientResult
186 | validate := util.Validator{}
187 | ID := c.Param("id")
188 |
189 | if validate.IsEmpty(ID) || !validate.IsUUID4(ID) {
190 | c.JSON(http.StatusBadRequest, gin.H{
191 | "status": "error",
192 | "error": "Client ID is invalid.",
193 | })
194 | return
195 | }
196 |
197 | client := api.Client{}
198 | channel := api.Channel{}
199 |
200 | rawBody, err := c.GetRawData()
201 |
202 | if err != nil {
203 | c.JSON(http.StatusBadRequest, gin.H{
204 | "status": "error",
205 | "error": "Invalid request",
206 | })
207 | return
208 | }
209 |
210 | ok, err := clientResult.LoadFromJSON(rawBody)
211 |
212 | if !ok || err != nil {
213 | c.JSON(http.StatusBadRequest, gin.H{
214 | "status": "error",
215 | "error": "Invalid request",
216 | })
217 | return
218 | }
219 |
220 | if !validate.IsSlugs(clientResult.Channels, 3, 60) {
221 | c.JSON(http.StatusBadRequest, gin.H{
222 | "status": "error",
223 | "error": "Provided client channels are invalid.",
224 | })
225 | return
226 | }
227 |
228 | if !client.Init() || !channel.Init() {
229 | c.JSON(http.StatusServiceUnavailable, gin.H{
230 | "status": "error",
231 | "error": "Internal server error",
232 | })
233 | return
234 | }
235 |
236 | ok, err = channel.ChannelsExist(clientResult.Channels)
237 |
238 | if !ok || err != nil {
239 | c.JSON(http.StatusBadRequest, gin.H{
240 | "status": "error",
241 | "error": err.Error(),
242 | })
243 | return
244 | }
245 |
246 | ok, err = client.Unsubscribe(ID, clientResult.Channels)
247 |
248 | if !ok || err != nil {
249 | c.JSON(http.StatusBadRequest, gin.H{
250 | "status": "error",
251 | "error": err.Error(),
252 | })
253 | return
254 | }
255 |
256 | c.Status(http.StatusOK)
257 | }
258 |
259 | // Subscribe controller
260 | func Subscribe(c *gin.Context) {
261 |
262 | var clientResult api.ClientResult
263 | validate := util.Validator{}
264 | ID := c.Param("id")
265 |
266 | if validate.IsEmpty(ID) || !validate.IsUUID4(ID) {
267 | c.JSON(http.StatusBadRequest, gin.H{
268 | "status": "error",
269 | "error": "Client ID is invalid.",
270 | })
271 | return
272 | }
273 |
274 | client := api.Client{}
275 | channel := api.Channel{}
276 |
277 | rawBody, err := c.GetRawData()
278 |
279 | if err != nil {
280 | c.JSON(http.StatusBadRequest, gin.H{
281 | "status": "error",
282 | "error": "Invalid request",
283 | })
284 | return
285 | }
286 |
287 | ok, err := clientResult.LoadFromJSON(rawBody)
288 |
289 | if !ok || err != nil {
290 | c.JSON(http.StatusBadRequest, gin.H{
291 | "status": "error",
292 | "error": "Invalid request",
293 | })
294 | return
295 | }
296 |
297 | if !validate.IsSlugs(clientResult.Channels, 3, 60) {
298 | c.JSON(http.StatusBadRequest, gin.H{
299 | "status": "error",
300 | "error": "Provided client channels are invalid.",
301 | })
302 | return
303 | }
304 |
305 | if !client.Init() || !channel.Init() {
306 | c.JSON(http.StatusServiceUnavailable, gin.H{
307 | "status": "error",
308 | "error": "Internal server error",
309 | })
310 | return
311 | }
312 |
313 | ok, err = channel.ChannelsExist(clientResult.Channels)
314 |
315 | if !ok || err != nil {
316 | c.JSON(http.StatusBadRequest, gin.H{
317 | "status": "error",
318 | "error": err.Error(),
319 | })
320 | return
321 | }
322 |
323 | ok, err = client.Subscribe(ID, clientResult.Channels)
324 |
325 | if !ok || err != nil {
326 | c.JSON(http.StatusBadRequest, gin.H{
327 | "status": "error",
328 | "error": err.Error(),
329 | })
330 | return
331 | }
332 |
333 | c.Status(http.StatusOK)
334 | }
335 |
--------------------------------------------------------------------------------
/core/controller/config.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package controller
6 |
7 | import (
8 | "net/http"
9 |
10 | "github.com/clivern/beaver/core/api"
11 | "github.com/clivern/beaver/core/util"
12 |
13 | "github.com/gin-gonic/gin"
14 | )
15 |
16 | // GetConfigByKey controller
17 | func GetConfigByKey(c *gin.Context) {
18 | key := c.Param("key")
19 | validate := util.Validator{}
20 |
21 | if validate.IsEmpty(key) || !validate.IsSlug(key, 3, 60) {
22 | c.JSON(http.StatusBadRequest, gin.H{
23 | "status": "error",
24 | "error": "Config key must be alphanumeric with length from 3 to 60",
25 | })
26 | return
27 | }
28 |
29 | config := api.Config{}
30 |
31 | if !config.Init() {
32 | c.JSON(http.StatusServiceUnavailable, gin.H{
33 | "status": "error",
34 | "error": "Internal server error",
35 | })
36 | return
37 | }
38 |
39 | value, err := config.GetConfigByKey(key)
40 |
41 | if err != nil {
42 | c.JSON(http.StatusNotFound, gin.H{
43 | "status": "error",
44 | "error": err.Error(),
45 | })
46 | return
47 | }
48 |
49 | c.JSON(http.StatusOK, gin.H{
50 | "key": key,
51 | "value": value,
52 | })
53 | }
54 |
55 | // CreateConfig controller
56 | func CreateConfig(c *gin.Context) {
57 |
58 | validate := util.Validator{}
59 | var configRequest api.ConfigResult
60 |
61 | config := api.Config{}
62 |
63 | rawBody, err := c.GetRawData()
64 |
65 | if err != nil {
66 | c.JSON(http.StatusBadRequest, gin.H{
67 | "status": "error",
68 | "error": "Invalid request",
69 | })
70 | return
71 | }
72 |
73 | ok, err := configRequest.LoadFromJSON(rawBody)
74 |
75 | if !ok || err != nil {
76 | c.JSON(http.StatusBadRequest, gin.H{
77 | "status": "error",
78 | "error": "Invalid request",
79 | })
80 | return
81 | }
82 |
83 | if validate.IsEmpty(configRequest.Key) || !validate.IsSlug(configRequest.Key, 3, 60) {
84 | c.JSON(http.StatusBadRequest, gin.H{
85 | "status": "error",
86 | "error": "Config key must be alphanumeric with length from 3 to 60",
87 | })
88 | return
89 | }
90 |
91 | if validate.IsEmpty(configRequest.Value) {
92 | c.JSON(http.StatusBadRequest, gin.H{
93 | "status": "error",
94 | "error": "Config value must not be empty",
95 | })
96 | return
97 | }
98 |
99 | if !config.Init() {
100 | c.JSON(http.StatusServiceUnavailable, gin.H{
101 | "status": "error",
102 | "error": "Internal server error",
103 | })
104 | return
105 | }
106 |
107 | _, err = config.CreateConfig(configRequest.Key, configRequest.Value)
108 |
109 | if err != nil {
110 | c.JSON(http.StatusBadRequest, gin.H{
111 | "status": "error",
112 | "error": err.Error(),
113 | })
114 | return
115 | }
116 |
117 | c.Status(http.StatusCreated)
118 | }
119 |
120 | // DeleteConfigByKey controller
121 | func DeleteConfigByKey(c *gin.Context) {
122 | key := c.Param("key")
123 | validate := util.Validator{}
124 |
125 | if validate.IsEmpty(key) || !validate.IsSlug(key, 3, 60) {
126 | c.JSON(http.StatusBadRequest, gin.H{
127 | "status": "error",
128 | "error": "Config key must be alphanumeric with length from 3 to 60",
129 | })
130 | return
131 | }
132 |
133 | config := api.Config{}
134 |
135 | if !config.Init() {
136 | c.JSON(http.StatusServiceUnavailable, gin.H{
137 | "status": "error",
138 | "error": "Internal server error",
139 | })
140 | return
141 | }
142 |
143 | _, err := config.DeleteConfigByKey(key)
144 |
145 | if err != nil {
146 | c.JSON(http.StatusNotFound, gin.H{
147 | "status": "error",
148 | "error": err.Error(),
149 | })
150 | return
151 | }
152 |
153 | c.Status(http.StatusNoContent)
154 | }
155 |
156 | // UpdateConfigByKey controller
157 | func UpdateConfigByKey(c *gin.Context) {
158 |
159 | var configRequest api.ConfigResult
160 | validate := util.Validator{}
161 |
162 | config := api.Config{}
163 |
164 | rawBody, err := c.GetRawData()
165 |
166 | if err != nil {
167 | c.JSON(http.StatusBadRequest, gin.H{
168 | "status": "error",
169 | "error": "Invalid request",
170 | })
171 | return
172 | }
173 |
174 | configRequest.LoadFromJSON(rawBody)
175 | configRequest.Key = c.Param("key")
176 |
177 | if validate.IsEmpty(configRequest.Key) || !validate.IsSlug(configRequest.Key, 3, 60) {
178 | c.JSON(http.StatusBadRequest, gin.H{
179 | "status": "error",
180 | "error": "Config key must be alphanumeric with length from 3 to 60",
181 | })
182 | return
183 | }
184 |
185 | if validate.IsEmpty(configRequest.Value) {
186 | c.JSON(http.StatusBadRequest, gin.H{
187 | "status": "error",
188 | "error": "Config value must not be empty",
189 | })
190 | return
191 | }
192 |
193 | if !config.Init() {
194 | c.JSON(http.StatusServiceUnavailable, gin.H{
195 | "status": "error",
196 | "error": "Internal server error",
197 | })
198 | return
199 | }
200 |
201 | _, err = config.UpdateConfigByKey(configRequest.Key, configRequest.Value)
202 |
203 | if err != nil {
204 | c.JSON(http.StatusBadRequest, gin.H{
205 | "status": "error",
206 | "error": err.Error(),
207 | })
208 | return
209 | }
210 |
211 | c.Status(http.StatusOK)
212 | }
213 |
--------------------------------------------------------------------------------
/core/controller/health.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package controller
6 |
7 | import (
8 | "net/http"
9 |
10 | "github.com/gin-gonic/gin"
11 | )
12 |
13 | // Health controller
14 | func Health(c *gin.Context) {
15 | c.JSON(http.StatusOK, gin.H{
16 | "status": "ok",
17 | })
18 | }
19 |
--------------------------------------------------------------------------------
/core/controller/home.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package controller
6 |
7 | import (
8 | "net/http"
9 |
10 | "github.com/gin-gonic/gin"
11 | )
12 |
13 | // Home controller
14 | func Home(c *gin.Context) {
15 | homeTpl := ` Beaver Beaver
A Real Time Messaging Server.
`
16 | c.Writer.WriteHeader(http.StatusOK)
17 | c.Writer.Write([]byte(homeTpl))
18 | return
19 | }
20 |
--------------------------------------------------------------------------------
/core/controller/metrics.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package controller
6 |
7 | import (
8 | "net/http"
9 |
10 | "github.com/prometheus/client_golang/prometheus/promhttp"
11 | )
12 |
13 | // Metrics controller
14 | func Metrics() http.Handler {
15 | return promhttp.Handler()
16 | }
17 |
--------------------------------------------------------------------------------
/core/controller/socket.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package controller
6 |
7 | import (
8 | "encoding/json"
9 | "net/http"
10 |
11 | "github.com/clivern/beaver/core/api"
12 | "github.com/clivern/beaver/core/util"
13 |
14 | "github.com/gin-gonic/gin"
15 | "github.com/gorilla/websocket"
16 | log "github.com/sirupsen/logrus"
17 | )
18 |
19 | // Message struct
20 | type Message struct {
21 | FromClient string `json:"from_client"`
22 | ToClient string `json:"to_client"`
23 | Channel string `json:"channel"`
24 | Data string `json:"data"`
25 | }
26 |
27 | // BroadcastRequest struct
28 | type BroadcastRequest struct {
29 | Channels []string `json:"channels"`
30 | Data string `json:"data"`
31 | }
32 |
33 | // PublishRequest struct
34 | type PublishRequest struct {
35 | Channel string `json:"channel"`
36 | Data string `json:"data"`
37 | }
38 |
39 | // Websocket Object
40 | type Websocket struct {
41 | Clients util.Map
42 | Broadcast chan Message
43 | Upgrader websocket.Upgrader
44 | }
45 |
46 | // IsValid checks if message is valid
47 | func (m *Message) IsValid() bool {
48 | validator := util.Validator{}
49 | return validator.IsJSON(m.Data)
50 | }
51 |
52 | // LoadFromJSON load object from json
53 | func (c *BroadcastRequest) LoadFromJSON(data []byte) (bool, error) {
54 | err := json.Unmarshal(data, &c)
55 | if err != nil {
56 | return false, err
57 | }
58 | return true, nil
59 | }
60 |
61 | // ConvertToJSON converts object to json
62 | func (c *BroadcastRequest) ConvertToJSON() (string, error) {
63 | data, err := json.Marshal(&c)
64 | if err != nil {
65 | return "", err
66 | }
67 | return string(data), nil
68 | }
69 |
70 | // LoadFromJSON load object from json
71 | func (c *PublishRequest) LoadFromJSON(data []byte) (bool, error) {
72 | err := json.Unmarshal(data, &c)
73 | if err != nil {
74 | return false, err
75 | }
76 | return true, nil
77 | }
78 |
79 | // ConvertToJSON converts object to json
80 | func (c *PublishRequest) ConvertToJSON() (string, error) {
81 | data, err := json.Marshal(&c)
82 | if err != nil {
83 | return "", err
84 | }
85 | return string(data), nil
86 | }
87 |
88 | // Init initialize the websocket object
89 | func (e *Websocket) Init() {
90 | e.Clients = util.NewMap()
91 | e.Broadcast = make(chan Message)
92 | e.Upgrader = websocket.Upgrader{
93 | ReadBufferSize: 1024,
94 | WriteBufferSize: 1024,
95 | CheckOrigin: func(_ *http.Request) bool {
96 | return true
97 | },
98 | }
99 | }
100 |
101 | // HandleConnections manage new clients
102 | func (e *Websocket) HandleConnections(w http.ResponseWriter, r *http.Request, ID string, token string) {
103 |
104 | var clientResult api.ClientResult
105 | validate := util.Validator{}
106 |
107 | // Validate client uuid & token
108 | if validate.IsEmpty(ID) || validate.IsEmpty(token) || !validate.IsUUID4(ID) {
109 | return
110 | }
111 |
112 | client := api.Client{}
113 |
114 | if !client.Init() {
115 | return
116 | }
117 |
118 | clientResult, err := client.GetClientByID(ID)
119 |
120 | if err != nil {
121 | return
122 | }
123 |
124 | // Ensure that client is alreay registered before
125 | if clientResult.ID != ID || clientResult.Token != token {
126 | return
127 | }
128 |
129 | ok, err := client.Connect(clientResult)
130 |
131 | if !ok || err != nil {
132 | return
133 | }
134 |
135 | // Upgrade initial GET request to a websocket
136 | ws, err := e.Upgrader.Upgrade(w, r, nil)
137 |
138 | if err != nil {
139 | log.Fatalf(
140 | `Error while upgrading the GET request to a websocket for client %s: %s`,
141 | ID,
142 | err.Error(),
143 | )
144 | }
145 |
146 | // Make sure we close the connection when the function returns
147 | defer ws.Close()
148 |
149 | // Register our new client
150 | e.Clients.Set(ID, ws)
151 |
152 | log.Infof(
153 | `Client %s connected`,
154 | ID,
155 | )
156 |
157 | for {
158 | var msg Message
159 |
160 | // Read in a new message as JSON and map it to a Message object
161 | err := ws.ReadJSON(&msg)
162 |
163 | if err != nil {
164 | e.Clients.Delete(ID)
165 | client.Disconnect(clientResult)
166 | log.Infof(
167 | `Client %s disconnected`,
168 | ID,
169 | )
170 | break
171 | }
172 |
173 | msg.FromClient = ID
174 |
175 | if msg.IsValid() {
176 | // Send the newly received message to the broadcast channel
177 | e.Broadcast <- msg
178 | }
179 | }
180 | }
181 |
182 | // HandleMessages send messages to a specific connected client
183 | func (e *Websocket) HandleMessages() {
184 |
185 | validate := util.Validator{}
186 |
187 | for {
188 | // Grab the next message from the broadcast channel
189 | msg := <-e.Broadcast
190 |
191 | // Send to Client
192 | if msg.IsValid() && !validate.IsEmpty(msg.ToClient) && !validate.IsEmpty(msg.Channel) && validate.IsUUID4(msg.ToClient) {
193 | // Push message to that client if it still connected
194 | // or remove from clients if we can't deliver messages to
195 | // it anymore
196 | if client, ok := e.Clients.Get(msg.ToClient); ok {
197 | err := client.(*websocket.Conn).WriteJSON(msg)
198 |
199 | if err != nil {
200 | client.(*websocket.Conn).Close()
201 | e.Clients.Delete(msg.ToClient)
202 | }
203 | }
204 | }
205 |
206 | // Send to client Peers on a channel
207 | if msg.IsValid() && !validate.IsEmpty(msg.FromClient) && !validate.IsEmpty(msg.Channel) && validate.IsUUID4(msg.FromClient) {
208 |
209 | channel := api.Channel{}
210 | channel.Init()
211 | iter := channel.ChannelScan(msg.Channel).Iterator()
212 |
213 | for iter.Next() {
214 |
215 | if msg.FromClient == iter.Val() {
216 | continue
217 | }
218 |
219 | msg.ToClient = iter.Val()
220 |
221 | if msg.ToClient != "" && validate.IsUUID4(msg.ToClient) {
222 | if client, ok := e.Clients.Get(msg.ToClient); ok {
223 | err := client.(*websocket.Conn).WriteJSON(msg)
224 | if err != nil {
225 | client.(*websocket.Conn).Close()
226 | e.Clients.Delete(msg.ToClient)
227 | }
228 | }
229 | }
230 | }
231 | }
232 | }
233 | }
234 |
235 | // BroadcastAction controller
236 | func (e *Websocket) BroadcastAction(c *gin.Context, rawBody []byte) {
237 |
238 | var broadcastRequest BroadcastRequest
239 | var key string
240 | var msg Message
241 |
242 | validate := util.Validator{}
243 |
244 | broadcastRequest.LoadFromJSON(rawBody)
245 |
246 | if !validate.IsSlugs(broadcastRequest.Channels, 3, 60) {
247 | c.JSON(http.StatusBadRequest, gin.H{
248 | "status": "error",
249 | "error": "Provided client channels are invalid.",
250 | })
251 | return
252 | }
253 |
254 | channel := api.Channel{}
255 |
256 | if !channel.Init() {
257 | c.JSON(http.StatusServiceUnavailable, gin.H{
258 | "status": "error",
259 | "error": "Internal server error",
260 | })
261 | return
262 | }
263 |
264 | ok, err := channel.ChannelsExist(broadcastRequest.Channels)
265 |
266 | if !ok || err != nil {
267 | c.JSON(http.StatusBadRequest, gin.H{
268 | "status": "error",
269 | "error": err.Error(),
270 | })
271 | return
272 | }
273 |
274 | if !validate.IsJSON(broadcastRequest.Data) {
275 | c.JSON(http.StatusBadRequest, gin.H{
276 | "status": "error",
277 | "error": "Message data is invalid JSON",
278 | })
279 | return
280 | }
281 |
282 | for _, name := range broadcastRequest.Channels {
283 | // Push message to all subscribed clients
284 | iter := channel.ChannelScan(name).Iterator()
285 |
286 | for iter.Next() {
287 | key = iter.Val()
288 | if key != "" && validate.IsUUID4(key) {
289 | msg = Message{
290 | ToClient: key,
291 | Data: broadcastRequest.Data,
292 | Channel: name,
293 | }
294 |
295 | e.Broadcast <- msg
296 | }
297 | }
298 | }
299 |
300 | c.Status(http.StatusOK)
301 | }
302 |
303 | // PublishAction controller
304 | func (e *Websocket) PublishAction(c *gin.Context, rawBody []byte) {
305 |
306 | var publishRequest PublishRequest
307 | var key string
308 | var msg Message
309 |
310 | validate := util.Validator{}
311 |
312 | publishRequest.LoadFromJSON(rawBody)
313 |
314 | if !validate.IsSlug(publishRequest.Channel, 3, 60) {
315 | c.JSON(http.StatusBadRequest, gin.H{
316 | "status": "error",
317 | "error": "Provided client channel is invalid.",
318 | })
319 | return
320 | }
321 |
322 | channel := api.Channel{}
323 |
324 | if !channel.Init() {
325 | c.JSON(http.StatusServiceUnavailable, gin.H{
326 | "status": "error",
327 | "error": "Internal server error",
328 | })
329 | return
330 | }
331 |
332 | ok, err := channel.ChannelExist(publishRequest.Channel)
333 |
334 | if !ok || err != nil {
335 | c.JSON(http.StatusBadRequest, gin.H{
336 | "status": "error",
337 | "error": err.Error(),
338 | })
339 | return
340 | }
341 |
342 | if !validate.IsJSON(publishRequest.Data) {
343 | c.JSON(http.StatusBadRequest, gin.H{
344 | "status": "error",
345 | "error": "Message data is invalid JSON",
346 | })
347 | return
348 | }
349 |
350 | // Push message to all subscribed clients
351 | iter := channel.ChannelScan(publishRequest.Channel).Iterator()
352 |
353 | for iter.Next() {
354 | key = iter.Val()
355 | if key != "" && validate.IsUUID4(key) {
356 | msg = Message{
357 | ToClient: key,
358 | Data: publishRequest.Data,
359 | Channel: publishRequest.Channel,
360 | }
361 |
362 | e.Broadcast <- msg
363 | }
364 | }
365 |
366 | c.Status(http.StatusOK)
367 | }
368 |
--------------------------------------------------------------------------------
/core/driver/redis.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package driver
6 |
7 | import (
8 | "sync"
9 | "time"
10 |
11 | "github.com/go-redis/redis"
12 | "github.com/spf13/viper"
13 | )
14 |
15 | var instance *redis.Client
16 |
17 | // Redis driver
18 | type Redis struct {
19 | sync.RWMutex
20 | Client *redis.Client
21 | Addr string
22 | Password string
23 | DB int
24 | }
25 |
26 | // NewRedisDriver create a new instance
27 | func NewRedisDriver() *Redis {
28 | return &Redis{
29 | Addr: viper.GetString("app.database.redis.address"),
30 | Password: viper.GetString("app.database.redis.password"),
31 | DB: viper.GetInt("app.database.redis.db"),
32 | }
33 | }
34 |
35 | // Connect establish a redis connection
36 | func (r *Redis) Connect() (bool, error) {
37 | r.Lock()
38 | defer r.Unlock()
39 |
40 | // Reuse redis connections
41 | if instance == nil {
42 | r.Client = redis.NewClient(&redis.Options{
43 | Addr: r.Addr,
44 | Password: r.Password,
45 | DB: r.DB,
46 | PoolSize: 10,
47 | PoolTimeout: 30 * time.Second,
48 | })
49 |
50 | instance = r.Client
51 |
52 | _, err := r.Ping()
53 |
54 | if err != nil {
55 | return false, err
56 | }
57 | } else {
58 | r.Client = instance
59 |
60 | _, err := r.Ping()
61 |
62 | if err != nil {
63 | return false, err
64 | }
65 | }
66 |
67 | return true, nil
68 | }
69 |
70 | // Ping checks the redis connection
71 | func (r *Redis) Ping() (bool, error) {
72 | pong, err := r.Client.Ping().Result()
73 |
74 | if err != nil {
75 | return false, err
76 | }
77 | return pong == "PONG", nil
78 | }
79 |
80 | // Set sets a record
81 | func (r *Redis) Set(key, value string, expiration time.Duration) (bool, error) {
82 | result := r.Client.Set(key, value, expiration)
83 |
84 | if result.Err() != nil {
85 | return false, result.Err()
86 | }
87 |
88 | return result.Val() == "OK", nil
89 | }
90 |
91 | // Get gets a record value
92 | func (r *Redis) Get(key string) (string, error) {
93 | result := r.Client.Get(key)
94 |
95 | if result.Err() != nil {
96 | return "", result.Err()
97 | }
98 |
99 | return result.Val(), nil
100 | }
101 |
102 | // Exists deletes a record
103 | func (r *Redis) Exists(key string) (bool, error) {
104 | result := r.Client.Exists(key)
105 |
106 | if result.Err() != nil {
107 | return false, result.Err()
108 | }
109 |
110 | return result.Val() > 0, nil
111 | }
112 |
113 | // Del deletes a record
114 | func (r *Redis) Del(key string) (int64, error) {
115 | result := r.Client.Del(key)
116 |
117 | if result.Err() != nil {
118 | return 0, result.Err()
119 | }
120 |
121 | return result.Val(), nil
122 | }
123 |
124 | // HGet gets a record from hash
125 | func (r *Redis) HGet(key, field string) (string, error) {
126 | result := r.Client.HGet(key, field)
127 |
128 | if result.Err() != nil {
129 | return "", result.Err()
130 | }
131 |
132 | return result.Val(), nil
133 | }
134 |
135 | // HSet sets a record in hash
136 | func (r *Redis) HSet(key, field, value string) (bool, error) {
137 | result := r.Client.HSet(key, field, value)
138 |
139 | if result.Err() != nil {
140 | return false, result.Err()
141 | }
142 |
143 | return result.Val(), nil
144 | }
145 |
146 | // HExists checks if key exists on a hash
147 | func (r *Redis) HExists(key, field string) (bool, error) {
148 | result := r.Client.HExists(key, field)
149 |
150 | if result.Err() != nil {
151 | return false, result.Err()
152 | }
153 |
154 | return result.Val(), nil
155 | }
156 |
157 | // HDel deletes a hash record
158 | func (r *Redis) HDel(key, field string) (int64, error) {
159 | result := r.Client.HDel(key, field)
160 |
161 | if result.Err() != nil {
162 | return 0, result.Err()
163 | }
164 |
165 | return result.Val(), nil
166 | }
167 |
168 | // HLen count hash records
169 | func (r *Redis) HLen(key string) (int64, error) {
170 | result := r.Client.HLen(key)
171 |
172 | if result.Err() != nil {
173 | return 0, result.Err()
174 | }
175 |
176 | return result.Val(), nil
177 | }
178 |
179 | // HTruncate deletes a hash
180 | func (r *Redis) HTruncate(key string) (int64, error) {
181 | result := r.Client.Del(key)
182 |
183 | if result.Err() != nil {
184 | return 0, result.Err()
185 | }
186 |
187 | return result.Val(), nil
188 | }
189 |
190 | // HScan return an iterative obj for a hash
191 | func (r *Redis) HScan(key string, cursor uint64, match string, count int64) *redis.ScanCmd {
192 | return r.Client.HScan(key, cursor, match, count)
193 | }
194 |
--------------------------------------------------------------------------------
/core/middleware/auth.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package middleware
6 |
7 | import (
8 | "net/http"
9 | "strings"
10 |
11 | "github.com/gin-gonic/gin"
12 | "github.com/spf13/viper"
13 | )
14 |
15 | // Auth middleware
16 | func Auth() gin.HandlerFunc {
17 | return func(c *gin.Context) {
18 | path := c.Request.URL.Path
19 |
20 | if strings.Contains(path, "/api/") {
21 | apiKey := c.GetHeader("x-api-key")
22 | if apiKey != viper.GetString("app.api.key") && viper.GetString("app.api.key") != "" {
23 | c.AbortWithStatus(http.StatusUnauthorized)
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/core/middleware/correlation.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package middleware
6 |
7 | import (
8 | "strings"
9 |
10 | "github.com/clivern/beaver/core/util"
11 |
12 | "github.com/gin-gonic/gin"
13 | )
14 |
15 | // Correlation middleware
16 | func Correlation() gin.HandlerFunc {
17 | return func(c *gin.Context) {
18 | corralationID := c.GetHeader("x-correlation-id")
19 |
20 | if strings.TrimSpace(corralationID) == "" {
21 | c.Request.Header.Add("X-Correlation-ID", util.GenerateUUID4())
22 | }
23 | c.Next()
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/core/middleware/cors.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package middleware
6 |
7 | import (
8 | "net/http"
9 |
10 | "github.com/gin-gonic/gin"
11 | )
12 |
13 | // Cors middleware
14 | func Cors() gin.HandlerFunc {
15 | return func(c *gin.Context) {
16 | c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
17 | c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
18 | c.Writer.Header().Set("Access-Control-Allow-Headers", "*")
19 | c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
20 |
21 | if c.Request.Method == "OPTIONS" {
22 | c.AbortWithStatus(http.StatusOK)
23 | return
24 | }
25 |
26 | c.Next()
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/core/middleware/log.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package middleware
6 |
7 | import (
8 | "bytes"
9 | "io/ioutil"
10 |
11 | "github.com/gin-gonic/gin"
12 | log "github.com/sirupsen/logrus"
13 | )
14 |
15 | // Logger middleware
16 | // TODO Hide Secure Data from Logs
17 | func Logger() gin.HandlerFunc {
18 | return func(c *gin.Context) {
19 | // before request
20 | var bodyBytes []byte
21 |
22 | // Workaround for issue https://github.com/gin-gonic/gin/issues/1651
23 | if c.Request.Body != nil {
24 | bodyBytes, _ = ioutil.ReadAll(c.Request.Body)
25 | }
26 |
27 | c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
28 |
29 | log.WithFields(log.Fields{
30 | "correlation_id": c.GetHeader("x-correlation-id"),
31 | "http_method": c.Request.Method,
32 | "http_path": c.Request.URL.Path,
33 | "request_body": string(bodyBytes),
34 | }).Info("Request started")
35 |
36 | c.Next()
37 |
38 | // after request
39 | status := c.Writer.Status()
40 | size := c.Writer.Size()
41 |
42 | log.WithFields(log.Fields{
43 | "correlation_id": c.GetHeader("x-correlation-id"),
44 | "http_status": status,
45 | "response_size": size,
46 | }).Info(`Request finished`)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/core/middleware/metric.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package middleware
6 |
7 | import (
8 | "strconv"
9 | "time"
10 |
11 | "github.com/gin-gonic/gin"
12 | "github.com/prometheus/client_golang/prometheus"
13 | log "github.com/sirupsen/logrus"
14 | )
15 |
16 | var (
17 | httpRequests = prometheus.NewCounterVec(
18 | prometheus.CounterOpts{
19 | Namespace: "beaver",
20 | Name: "total_http_requests",
21 | Help: "How many HTTP requests processed, partitioned by status code and HTTP method.",
22 | }, []string{"code", "method", "handler", "host", "url"})
23 |
24 | requestDuration = prometheus.NewHistogramVec(
25 | prometheus.HistogramOpts{
26 | Subsystem: "beaver",
27 | Name: "request_duration_seconds",
28 | Help: "The HTTP request latencies in seconds.",
29 | },
30 | []string{"code", "method", "url"},
31 | )
32 |
33 | responseSize = prometheus.NewSummary(
34 | prometheus.SummaryOpts{
35 | Namespace: "beaver",
36 | Name: "response_size_bytes",
37 | Help: "The HTTP response sizes in bytes.",
38 | },
39 | )
40 | )
41 |
42 | func init() {
43 | prometheus.MustRegister(httpRequests)
44 | prometheus.MustRegister(requestDuration)
45 | prometheus.MustRegister(responseSize)
46 | }
47 |
48 | // Metric middleware
49 | func Metric() gin.HandlerFunc {
50 | return func(c *gin.Context) {
51 | // before request
52 | start := time.Now()
53 |
54 | c.Next()
55 |
56 | // after request
57 | elapsed := float64(time.Since(start)) / float64(time.Second)
58 |
59 | log.WithFields(log.Fields{
60 | "correlation_id": c.GetHeader("x-correlation-id"),
61 | }).Info(`Collecting metrics`)
62 |
63 | // Collect Metrics
64 | httpRequests.WithLabelValues(
65 | strconv.Itoa(c.Writer.Status()),
66 | c.Request.Method,
67 | c.HandlerName(),
68 | c.Request.Host,
69 | c.Request.URL.Path,
70 | ).Inc()
71 |
72 | requestDuration.WithLabelValues(
73 | strconv.Itoa(c.Writer.Status()),
74 | c.Request.Method,
75 | c.Request.URL.Path,
76 | ).Observe(elapsed)
77 |
78 | responseSize.Observe(float64(c.Writer.Size()))
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/core/util/helpers.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package util
6 |
7 | import (
8 | "os"
9 |
10 | "github.com/satori/go.uuid"
11 | )
12 |
13 | // GenerateUUID4 create a UUID
14 | func GenerateUUID4() string {
15 | u := uuid.Must(uuid.NewV4(), nil)
16 | return u.String()
17 | }
18 |
19 | // Unset remove element at position i
20 | func Unset(a []string, i int) []string {
21 | a[i] = a[len(a)-1]
22 | a[len(a)-1] = ""
23 | return a[:len(a)-1]
24 | }
25 |
26 | // FileExists reports whether the named file exists
27 | func FileExists(path string) bool {
28 | if fi, err := os.Stat(path); err == nil {
29 | if fi.Mode().IsRegular() {
30 | return true
31 | }
32 | }
33 | return false
34 | }
35 |
36 | // DirExists reports whether the dir exists
37 | func DirExists(path string) bool {
38 | if fi, err := os.Stat(path); err == nil {
39 | if fi.Mode().IsDir() {
40 | return true
41 | }
42 | }
43 | return false
44 | }
45 |
46 | // EnsureDir ensures that directory exists
47 | func EnsureDir(dirName string, mode int) (bool, error) {
48 | err := os.MkdirAll(dirName, os.FileMode(mode))
49 |
50 | if err == nil || os.IsExist(err) {
51 | return true, nil
52 | }
53 | return false, err
54 | }
55 |
--------------------------------------------------------------------------------
/core/util/map.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package util
6 |
7 | import (
8 | "sync"
9 | )
10 |
11 | // Map type
12 | type Map struct {
13 | sync.RWMutex
14 | items map[string]interface{}
15 | }
16 |
17 | // NewMap creates a new instance of Map
18 | func NewMap() Map {
19 | return Map{items: make(map[string]interface{})}
20 | }
21 |
22 | // Get a key from a concurrent map
23 | func (cm *Map) Get(key string) (interface{}, bool) {
24 | cm.Lock()
25 | defer cm.Unlock()
26 |
27 | value, ok := cm.items[key]
28 |
29 | return value, ok
30 | }
31 |
32 | // Set a key in a concurrent map
33 | func (cm *Map) Set(key string, value interface{}) {
34 | cm.Lock()
35 | defer cm.Unlock()
36 |
37 | cm.items[key] = value
38 | }
39 |
40 | // Delete deletes a key
41 | func (cm *Map) Delete(key string) {
42 | cm.Lock()
43 | defer cm.Unlock()
44 |
45 | delete(cm.items, key)
46 | }
47 |
--------------------------------------------------------------------------------
/core/util/token.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package util
6 |
7 | import (
8 | "github.com/dgrijalva/jwt-go"
9 | )
10 |
11 | // GenerateJWTToken generate a jwt token for frontend
12 | func GenerateJWTToken(data string, timestamp int64, secret string) (string, error) {
13 |
14 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
15 | "data": data,
16 | "timestamp": timestamp,
17 | })
18 |
19 | tokenString, err := token.SignedString([]byte(secret))
20 |
21 | if err != nil {
22 | return "", err
23 | }
24 |
25 | return tokenString, nil
26 | }
27 |
--------------------------------------------------------------------------------
/core/util/validator.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package util
6 |
7 | import (
8 | "encoding/json"
9 | "regexp"
10 | "strings"
11 | )
12 |
13 | const (
14 | // UUID3 regex expr
15 | UUID3 string = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
16 | // UUID4 regex expr
17 | UUID4 string = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
18 | // UUID5 regex expr
19 | UUID5 string = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
20 | // UUID regex expr
21 | UUID string = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
22 | // SLUG regex expr
23 | SLUG string = "^[a-z0-9]+(?:_[a-z0-9]+)*$"
24 | )
25 |
26 | // Validator util
27 | type Validator struct {
28 | }
29 |
30 | // IsIn checks if item in array
31 | func (v *Validator) IsIn(item string, list []string) bool {
32 | for _, a := range list {
33 | if a == item {
34 | return true
35 | }
36 | }
37 | return false
38 | }
39 |
40 | // IsSlug checks if string is a valid slug
41 | func (v *Validator) IsSlug(slug string, min int, max int) bool {
42 |
43 | if len(slug) < min {
44 | return false
45 | }
46 |
47 | if len(slug) > max {
48 | return false
49 | }
50 |
51 | if regexp.MustCompile(SLUG).MatchString(slug) {
52 | return true
53 | }
54 |
55 | return false
56 | }
57 |
58 | // IsSlugs checks if string is a valid slug
59 | func (v *Validator) IsSlugs(slugs []string, min int, max int) bool {
60 |
61 | for _, slug := range slugs {
62 | if !v.IsSlug(slug, min, max) {
63 | return false
64 | }
65 | }
66 |
67 | return true
68 | }
69 |
70 | // IsEmpty checks if item is empty
71 | func (v *Validator) IsEmpty(item string) bool {
72 | if strings.TrimSpace(item) == "" {
73 | return true
74 | }
75 | return false
76 | }
77 |
78 | // IsUUID validates a UUID
79 | func (v *Validator) IsUUID(uuid string) bool {
80 | if regexp.MustCompile(UUID).MatchString(uuid) {
81 | return true
82 | }
83 |
84 | return false
85 | }
86 |
87 | // IsUUID3 validates a UUID3
88 | func (v *Validator) IsUUID3(uuid string) bool {
89 | if regexp.MustCompile(UUID3).MatchString(uuid) {
90 | return true
91 | }
92 |
93 | return false
94 | }
95 |
96 | // IsUUID4 validates a UUID4
97 | func (v *Validator) IsUUID4(uuid string) bool {
98 | if regexp.MustCompile(UUID4).MatchString(uuid) {
99 | return true
100 | }
101 |
102 | return false
103 | }
104 |
105 | // IsUUID5 validates a UUID5
106 | func (v *Validator) IsUUID5(uuid string) bool {
107 | if regexp.MustCompile(UUID5).MatchString(uuid) {
108 | return true
109 | }
110 |
111 | return false
112 | }
113 |
114 | // IsJSON validates a JSON string
115 | func (v *Validator) IsJSON(str string) bool {
116 | var jsonStr map[string]interface{}
117 | err := json.Unmarshal([]byte(str), &jsonStr)
118 | return err == nil
119 | }
120 |
--------------------------------------------------------------------------------
/deployment/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clivern/Beaver/bd68afd9d44386b789e46d303f7f4a865ac4cc68/deployment/.gitkeep
--------------------------------------------------------------------------------
/deployment/docker/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clivern/Beaver/bd68afd9d44386b789e46d303f7f4a865ac4cc68/deployment/docker/.gitkeep
--------------------------------------------------------------------------------
/deployment/linux/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clivern/Beaver/bd68afd9d44386b789e46d303f7f4a865ac4cc68/deployment/linux/.gitkeep
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/clivern/beaver
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible
7 | github.com/drone/envsubst v1.0.3
8 | github.com/gin-gonic/gin v1.9.1
9 | github.com/go-redis/redis v6.15.9+incompatible
10 | github.com/gorilla/websocket v1.5.0
11 | github.com/prometheus/client_golang v1.18.0
12 | github.com/satori/go.uuid v1.2.0
13 | github.com/sirupsen/logrus v1.9.3
14 | github.com/spf13/cobra v1.7.0
15 | github.com/spf13/viper v1.16.0
16 | )
17 |
18 | require (
19 | github.com/beorn7/perks v1.0.1 // indirect
20 | github.com/bytedance/sonic v1.9.1 // indirect
21 | github.com/cespare/xxhash/v2 v2.2.0 // indirect
22 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
23 | github.com/fsnotify/fsnotify v1.6.0 // indirect
24 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect
25 | github.com/gin-contrib/sse v0.1.0 // indirect
26 | github.com/go-playground/locales v0.14.1 // indirect
27 | github.com/go-playground/universal-translator v0.18.1 // indirect
28 | github.com/go-playground/validator/v10 v10.14.0 // indirect
29 | github.com/goccy/go-json v0.10.2 // indirect
30 | github.com/golang/protobuf v1.5.3 // indirect
31 | github.com/hashicorp/hcl v1.0.0 // indirect
32 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
33 | github.com/json-iterator/go v1.1.12 // indirect
34 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect
35 | github.com/leodido/go-urn v1.2.4 // indirect
36 | github.com/magiconair/properties v1.8.7 // indirect
37 | github.com/mattn/go-isatty v0.0.19 // indirect
38 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
39 | github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
40 | github.com/mitchellh/mapstructure v1.5.0 // indirect
41 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
42 | github.com/modern-go/reflect2 v1.0.2 // indirect
43 | github.com/onsi/ginkgo v1.16.5 // indirect
44 | github.com/onsi/gomega v1.27.10 // indirect
45 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect
46 | github.com/prometheus/client_model v0.5.0 // indirect
47 | github.com/prometheus/common v0.45.0 // indirect
48 | github.com/prometheus/procfs v0.12.0 // indirect
49 | github.com/spf13/afero v1.9.5 // indirect
50 | github.com/spf13/cast v1.5.1 // indirect
51 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
52 | github.com/spf13/pflag v1.0.5 // indirect
53 | github.com/subosito/gotenv v1.4.2 // indirect
54 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
55 | github.com/ugorji/go/codec v1.2.11 // indirect
56 | golang.org/x/arch v0.3.0 // indirect
57 | golang.org/x/crypto v0.14.0 // indirect
58 | golang.org/x/net v0.17.0 // indirect
59 | golang.org/x/sys v0.15.0 // indirect
60 | golang.org/x/text v0.13.0 // indirect
61 | google.golang.org/protobuf v1.31.0 // indirect
62 | gopkg.in/ini.v1 v1.67.0 // indirect
63 | gopkg.in/yaml.v3 v3.0.1 // indirect
64 | )
65 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
6 | cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
7 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
8 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
9 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
10 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
11 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
12 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
13 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
14 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
15 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
16 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
17 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
18 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
19 | cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
20 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
21 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
22 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
23 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
24 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
25 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
26 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
27 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
28 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
29 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
30 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
31 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
32 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
33 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
34 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
35 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
36 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
37 | cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
38 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
39 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
40 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
41 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
42 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
43 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
44 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
45 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
46 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
47 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
48 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
49 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
50 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
51 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
52 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
53 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
54 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
55 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
56 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
57 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
58 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
59 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
60 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
61 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
62 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
63 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
64 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
65 | github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
66 | github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
67 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
68 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
69 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
70 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
71 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
72 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
73 | github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
74 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
75 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
76 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
77 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
78 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
79 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
80 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
81 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
82 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
83 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
84 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
85 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
86 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
87 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
88 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
89 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
90 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
91 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
92 | github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
93 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
94 | github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
95 | github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
96 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
97 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
98 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
99 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
100 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
101 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
102 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
103 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
104 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
105 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
106 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
107 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
108 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
109 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
110 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
111 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
112 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
113 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
114 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
115 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
116 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
117 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
118 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
119 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
120 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
121 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
122 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
123 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
124 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
125 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
126 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
127 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
128 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
129 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
130 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
131 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
132 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
133 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
134 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
135 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
136 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
137 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
138 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
139 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
140 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
141 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
142 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
143 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
144 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
145 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
146 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
147 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
148 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
149 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
150 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
151 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
152 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
153 | github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
154 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
155 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
156 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
157 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
158 | github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
159 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
160 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
161 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
162 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
163 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
164 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
165 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
166 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
167 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
168 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
169 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
170 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
171 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
172 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
173 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
174 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
175 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
176 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
177 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
178 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
179 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
180 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
181 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
182 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
183 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
184 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
185 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
186 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
187 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
188 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
189 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
190 | github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
191 | github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
192 | github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
193 | github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
194 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
195 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
196 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
197 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
198 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
199 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
200 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
201 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
202 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
203 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
204 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
205 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
206 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
207 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
208 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
209 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
210 | github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
211 | github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
212 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
213 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
214 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
215 | github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
216 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
217 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
218 | github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
219 | github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
220 | github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
221 | github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
222 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
223 | github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
224 | github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
225 | github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
226 | github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
227 | github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
228 | github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
229 | github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
230 | github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
231 | github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
232 | github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
233 | github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
234 | github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
235 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
236 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
237 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
238 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
239 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
240 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
241 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
242 | github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
243 | github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
244 | github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
245 | github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
246 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
247 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
248 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
249 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
250 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
251 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
252 | github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
253 | github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
254 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
255 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
256 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
257 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
258 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
259 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
260 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
261 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
262 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
263 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
264 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
265 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
266 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
267 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
268 | github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
269 | github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
270 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
271 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
272 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
273 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
274 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
275 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
276 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
277 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
278 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
279 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
280 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
281 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
282 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
283 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
284 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
285 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
286 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
287 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
288 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
289 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
290 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
291 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
292 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
293 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
294 | golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
295 | golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
296 | golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
297 | golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
298 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
299 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
300 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
301 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
302 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
303 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
304 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
305 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
306 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
307 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
308 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
309 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
310 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
311 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
312 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
313 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
314 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
315 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
316 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
317 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
318 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
319 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
320 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
321 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
322 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
323 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
324 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
325 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
326 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
327 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
328 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
329 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
330 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
331 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
332 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
333 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
334 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
335 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
336 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
337 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
338 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
339 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
340 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
341 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
342 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
343 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
344 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
345 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
346 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
347 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
348 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
349 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
350 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
351 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
352 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
353 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
354 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
355 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
356 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
357 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
358 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
359 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
360 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
361 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
362 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
363 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
364 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
365 | golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
366 | golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
367 | golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
368 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
369 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
370 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
371 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
372 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
373 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
374 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
375 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
376 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
377 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
378 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
379 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
380 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
381 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
382 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
383 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
384 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
385 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
386 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
387 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
388 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
389 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
390 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
391 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
392 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
393 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
394 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
395 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
396 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
397 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
398 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
399 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
400 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
401 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
402 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
403 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
404 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
405 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
406 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
407 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
408 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
409 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
410 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
411 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
412 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
413 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
414 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
415 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
416 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
417 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
418 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
419 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
420 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
421 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
422 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
423 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
424 | golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
425 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
426 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
427 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
428 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
429 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
430 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
431 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
432 | golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
433 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
434 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
435 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
436 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
437 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
438 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
439 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
440 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
441 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
442 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
443 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
444 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
445 | golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
446 | golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
447 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
448 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
449 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
450 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
451 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
452 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
453 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
454 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
455 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
456 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
457 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
458 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
459 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
460 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
461 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
462 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
463 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
464 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
465 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
466 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
467 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
468 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
469 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
470 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
471 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
472 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
473 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
474 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
475 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
476 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
477 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
478 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
479 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
480 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
481 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
482 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
483 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
484 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
485 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
486 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
487 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
488 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
489 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
490 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
491 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
492 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
493 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
494 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
495 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
496 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
497 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
498 | golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
499 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
500 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
501 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
502 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
503 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
504 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
505 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
506 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
507 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
508 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
509 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
510 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
511 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
512 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
513 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
514 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
515 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
516 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
517 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
518 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
519 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
520 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
521 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
522 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
523 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
524 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
525 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
526 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
527 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
528 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
529 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
530 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
531 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
532 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
533 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
534 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
535 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
536 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
537 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
538 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
539 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
540 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
541 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
542 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
543 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
544 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
545 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
546 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
547 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
548 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
549 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
550 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
551 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
552 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
553 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
554 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
555 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
556 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
557 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
558 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
559 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
560 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
561 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
562 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
563 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
564 | google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
565 | google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
566 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
567 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
568 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
569 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
570 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
571 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
572 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
573 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
574 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
575 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
576 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
577 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
578 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
579 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
580 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
581 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
582 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
583 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
584 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
585 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
586 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
587 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
588 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
589 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
590 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
591 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
592 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
593 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
594 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
595 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
596 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
597 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
598 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
599 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
600 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
601 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
602 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
603 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
604 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
605 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
606 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
607 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
608 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
609 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
610 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
611 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
612 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
613 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
614 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
615 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
616 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
617 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
618 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
619 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
620 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
621 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
622 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
623 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
624 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/techstack.md:
--------------------------------------------------------------------------------
1 |
36 |
37 |
38 | # Tech Stack File
39 |  [Clivern/Beaver](https://github.com/Clivern/Beaver)
40 |
41 | |48
Tools used|12/21/23
Report generated|
42 | |------|------|
43 |
44 |
45 | ##
Languages (2)
46 |
47 |
48 |
49 |
50 | Golang
51 |
52 |
53 | |
54 |
55 |
56 |
57 |
58 | JavaScript
59 |
60 |
61 | |
62 |
63 |
64 |
65 |
66 | ##
Frameworks (2)
67 |
68 |
69 |
70 |
71 | Gin Gonic
72 |
73 |
74 | |
75 |
76 |
77 |
78 |
79 | Protobuf
80 |
81 |
82 | |
83 |
84 |
85 |
86 |
87 | ##
Data (1)
88 |
89 |
90 |
91 |
92 | Redis
93 |
94 |
95 | |
96 |
97 |
98 |
99 |
100 | ##
DevOps (3)
101 |
102 |
103 |
104 |
105 | Git
106 |
107 |
108 | |
109 |
110 |
111 |
112 |
113 | GitHub Actions
114 |
115 |
116 | |
117 |
118 |
119 |
120 |
121 | Prometheus
122 |
123 |
124 | |
125 |
126 |
127 |
128 |
129 | ## Other (1)
130 |
131 |
132 |
133 |
134 | Shell
135 |
136 |
137 | |
138 |
139 |
140 |
141 |
142 |
143 | ##
Open source packages (39)
144 |
145 | ##
Go Packages (39)
146 |
147 | |NAME|VERSION|LAST UPDATED|LAST UPDATED BY|LICENSE|VULNERABILITIES|
148 | |:------|:------|:------|:------|:------|:------|
149 | |[afero](https://pkg.go.dev/github.com/spf13/afero)|v1.9.5|04/24/23|Clivern |Apache-2.0|N/A|
150 | |[assertion](https://pkg.go.dev/github.com/onsi/gomega/internal/assertion)|N/A|04/24/23|Clivern |MIT|N/A|
151 | |[cast](https://pkg.go.dev/github.com/spf13/cast)|v1.5.1|04/24/23|Clivern |MIT|N/A|
152 | |[client_model](https://pkg.go.dev/github.com/prometheus/client_model)|v0.0.0|04/24/23|Clivern |Apache-2.0|N/A|
153 | |[cobra](https://pkg.go.dev/github.com/spf13/cobra)|v1.7.0|04/24/23|Clivern |Apache-2.0|N/A|
154 | |[codec](https://pkg.go.dev/github.com/ugorji/go/codec)|v1.2.11|04/24/23|Clivern |MIT|N/A|
155 | |[common](https://pkg.go.dev/github.com/prometheus/common)|v0.42.0|04/24/23|Clivern |Apache-2.0|N/A|
156 | |[convert](https://pkg.go.dev/github.com/onsi/ginkgo/ginkgo/convert)|N/A|01/08/19|Clivern |MIT|N/A|
157 | |[crypto](https://pkg.go.dev/golang.org/x/crypto)|v0.0.0|04/24/23|Clivern |BSD-3-Clause|[CVE-2020-9283](https://github.com/advisories/GHSA-ffhg-7mh4-33c4) (Moderate)|
158 | |[fsnotify](https://pkg.go.dev/github.com/fsnotify/fsnotify)|v1.4.7|04/24/23|Clivern |BSD-3-Clause|N/A|
159 | |[gin](https://pkg.go.dev/github.com/gin-gonic/gin)|v1.9.1|01/08/19|Clivern |MIT|N/A|
160 | |[ginkgo](https://pkg.go.dev/github.com/onsi/ginkgo)|v1.6.0|04/24/23|Clivern |MIT|N/A|
161 | |[go-isatty](https://pkg.go.dev/github.com/mattn/go-isatty)|v0.0.19|04/24/23|Clivern |MIT|N/A|
162 | |[go.uuid](https://pkg.go.dev/github.com/satori/go.uuid)|v1.2.0|03/14/19|Clivern |MIT|N/A|
163 | |[golang_protobuf_extensions](https://pkg.go.dev/github.com/matttproud/golang_protobuf_extensions)|v1.0.4|04/24/23|Clivern |Apache-2.0|N/A|
164 | |[gomega](https://pkg.go.dev/github.com/onsi/gomega)|v1.7.1|04/24/23|Clivern |MIT|N/A|
165 | |[gotenv](https://pkg.go.dev/github.com/subosito/gotenv)|v1.4.2|04/24/23|Clivern |MIT|N/A|
166 | |[hcl](https://pkg.go.dev/github.com/hashicorp/hcl)|v1.0.0|01/08/19|Clivern |MPL-2.0|N/A|
167 | |[ini.v1](https://pkg.go.dev/gopkg.in/ini.v1)|v1.67.0|04/24/23|Clivern |Apache-2.0|N/A|
168 | |[json-iterator/go](https://pkg.go.dev/github.com/json-iterator/go)|v1.1.12|04/24/23|Clivern |MIT|N/A|
169 | |[jwalterweatherman](https://pkg.go.dev/github.com/spf13/jwalterweatherman)|v1.1.0|04/24/23|Clivern |MIT|N/A|
170 | |[jwt-go](https://pkg.go.dev/github.com/dgrijalva/jwt-go)|N/A|03/14/19|Clivern |MIT|N/A|
171 | |[locales](https://pkg.go.dev/github.com/go-playground/locales)|v0.14.1|04/24/23|Clivern |MIT|N/A|
172 | |[logrus](https://pkg.go.dev/github.com/sirupsen/logrus)|v1.9.3|04/24/23|Clivern |MIT|N/A|
173 | |[mapstructure](https://pkg.go.dev/github.com/mitchellh/mapstructure)|v1.5.0|04/24/23|Clivern |MIT|N/A|
174 | |[mimetype](https://pkg.go.dev/github.com/gabriel-vasile/mimetype)|v1.4.2|04/24/23|Clivern |MIT|N/A|
175 | |[mousetrap](https://pkg.go.dev/github.com/inconshreveable/mousetrap)|v1.1.0|04/24/23|Clivern |Apache-2.0|N/A|
176 | |[net](https://pkg.go.dev/golang.org/x/net)|v0.0.0|01/08/19|Clivern |BSD-3-Clause|N/A|
177 | |[perks](https://pkg.go.dev/github.com/beorn7/perks)|v1.0.1|04/24/23|Clivern |MIT|N/A|
178 | |[pflag](https://pkg.go.dev/github.com/spf13/pflag)|v1.0.5|04/24/23|Clivern |BSD-3-Clause|N/A|
179 | |[procfs](https://pkg.go.dev/github.com/prometheus/procfs)|v0.10.1|04/24/23|Clivern |Apache-2.0|N/A|
180 | |[properties](https://pkg.go.dev/github.com/magiconair/properties)|v1.8.7|04/24/23|Clivern |BSD-2-Clause|N/A|
181 | |[proto](https://pkg.go.dev/github.com/golang/protobuf/proto)|N/A|04/24/23|Clivern |BSD-3-Clause|N/A|
182 | |[reflect2](https://pkg.go.dev/github.com/modern-go/reflect2)|v1.0.2|04/24/23|Clivern |Apache-2.0|N/A|
183 | |[sys](https://pkg.go.dev/golang.org/x/sys)|v0.0.0|01/08/19|Clivern |BSD-3-Clause|N/A|
184 | |[text](https://pkg.go.dev/golang.org/x/text)|v0.0.0|04/24/23|Clivern |BSD-3-Clause|N/A|
185 | |[universal-translator](https://pkg.go.dev/github.com/go-playground/universal-translator)|v0.18.1|04/24/23|Clivern |MIT|N/A|
186 | |[viper](https://pkg.go.dev/github.com/spf13/viper)|v1.16.0|04/24/23|Clivern |MIT|N/A|
187 | |[websocket](https://pkg.go.dev/github.com/gorilla/websocket)|v1.5.0|02/15/22|renovate[bot] |BSD-3-Clause|N/A|
188 |
189 |
190 |
191 |
192 | Generated via [Stack File](https://github.com/marketplace/stack-file)
193 |
--------------------------------------------------------------------------------
/techstack.yml:
--------------------------------------------------------------------------------
1 | repo_name: Clivern/Beaver
2 | report_id: 7bdb4c3f5f0b1a8883352e328dc9f3f5
3 | version: 0.1
4 | repo_type: Public
5 | timestamp: '2023-12-21T15:23:29+00:00'
6 | requested_by: Clivern
7 | provider: github
8 | branch: main
9 | detected_tools_count: 48
10 | tools:
11 | - name: Golang
12 | description: An open source programming language that makes it easy to build simple,
13 | reliable, and efficient software
14 | website_url: http://golang.org/
15 | license: BSD-3-Clause
16 | open_source: true
17 | hosted_saas: false
18 | category: Languages & Frameworks
19 | sub_category: Languages
20 | image_url: https://img.stackshare.io/service/1005/O6AczwfV_400x400.png
21 | detection_source: Repo Metadata
22 | - name: JavaScript
23 | description: Lightweight, interpreted, object-oriented language with first-class
24 | functions
25 | website_url: https://developer.mozilla.org/en-US/docs/Web/JavaScript
26 | open_source: true
27 | hosted_saas: false
28 | category: Languages & Frameworks
29 | sub_category: Languages
30 | image_url: https://img.stackshare.io/service/1209/javascript.jpeg
31 | detection_source: Repo Metadata
32 | - name: Gin Gonic
33 | description: 'HTTP web framework written in Go '
34 | website_url: https://gin-gonic.com/
35 | license: MIT
36 | open_source: true
37 | hosted_saas: false
38 | category: Languages & Frameworks
39 | sub_category: Frameworks (Full Stack)
40 | image_url: https://img.stackshare.io/service/4221/7894478.png
41 | detection_source: go.mod
42 | last_updated_by: Clivern
43 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
44 | - name: Protobuf
45 | description: Google's data interchange format
46 | website_url: https://developers.google.com/protocol-buffers/
47 | open_source: true
48 | hosted_saas: false
49 | category: Languages & Frameworks
50 | sub_category: Serialization Frameworks
51 | image_url: https://img.stackshare.io/service/4393/ma2jqJKH_400x400.png
52 | detection_source: go.mod
53 | last_updated_by: Clivern
54 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
55 | - name: Redis
56 | description: Open source (BSD licensed), in-memory data structure store
57 | website_url: http://redis.io/
58 | license: BSD-3-Clause
59 | open_source: true
60 | hosted_saas: false
61 | category: Data Stores
62 | sub_category: In-Memory Databases
63 | image_url: https://img.stackshare.io/service/1031/default_cbce472cd134adc6688572f999e9122b9657d4ba.png
64 | detection_source: go.mod
65 | last_updated_by: Ahmed⠙
66 | last_updated_on: 2020-12-31 20:42:54.000000000 Z
67 | - name: Git
68 | description: Fast, scalable, distributed revision control system
69 | website_url: http://git-scm.com/
70 | open_source: true
71 | hosted_saas: false
72 | category: Build, Test, Deploy
73 | sub_category: Version Control System
74 | image_url: https://img.stackshare.io/service/1046/git.png
75 | detection_source: Repo Metadata
76 | - name: GitHub Actions
77 | description: Automate your workflow from idea to production
78 | website_url: https://github.com/features/actions
79 | open_source: false
80 | hosted_saas: true
81 | category: Build, Test, Deploy
82 | sub_category: Continuous Integration
83 | image_url: https://img.stackshare.io/service/11563/actions.png
84 | detection_source: ".github/workflows/build.yml"
85 | last_updated_by: renovate[bot]
86 | last_updated_on: 2022-03-02 08:41:47.000000000 Z
87 | - name: Prometheus
88 | description: An open-source service monitoring system and time series database,
89 | developed by SoundCloud
90 | website_url: http://prometheus.io/
91 | license: Apache-2.0
92 | open_source: true
93 | hosted_saas: false
94 | category: Monitoring
95 | sub_category: Monitoring Tools
96 | image_url: https://img.stackshare.io/service/2501/default_3cf1b307194b26782be5cb209d30360580ae5b3c.png
97 | detection_source: go.mod
98 | last_updated_by: Clivern
99 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
100 | - name: Shell
101 | description: A shell is a text-based terminal, used for manipulating programs and
102 | files. Shell scripts typically manage program execution.
103 | website_url: https://en.wikipedia.org/wiki/Shell_script
104 | open_source: false
105 | hosted_saas: false
106 | category: Languages & Frameworks
107 | sub_category: Languages
108 | image_url: https://img.stackshare.io/service/4631/default_c2062d40130562bdc836c13dbca02d318205a962.png
109 | detection_source: Repo Metadata
110 | - name: afero
111 | description: A FileSystem Abstraction System for Go
112 | package_url: https://pkg.go.dev/github.com/spf13/afero
113 | version: 1.9.5
114 | license: Apache-2.0
115 | open_source: true
116 | hosted_saas: false
117 | category: Libraries
118 | sub_category: Go Modules Packages
119 | image_url: https://img.stackshare.io/package/go-packages/image.png
120 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
121 | detection_source: go.mod
122 | last_updated_by: Clivern
123 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
124 | - name: assertion
125 | description: Ginkgo's Preferred Matcher Library
126 | package_url: https://pkg.go.dev/github.com/onsi/gomega/internal/assertion
127 | license: MIT
128 | open_source: true
129 | hosted_saas: false
130 | category: Libraries
131 | sub_category: Go Modules Packages
132 | image_url: https://img.stackshare.io/package/go-packages/image.png
133 | detection_source: go.mod
134 | last_updated_by: Clivern
135 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
136 | - name: cast
137 | description: Safe and easy casting from one type to another in Go
138 | package_url: https://pkg.go.dev/github.com/spf13/cast
139 | version: 1.5.1
140 | license: MIT
141 | open_source: true
142 | hosted_saas: false
143 | category: Libraries
144 | sub_category: Go Modules Packages
145 | image_url: https://img.stackshare.io/package/go-packages/image.png
146 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
147 | detection_source: go.mod
148 | last_updated_by: Clivern
149 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
150 | - name: client_model
151 | description: Data model artifacts for Prometheus
152 | package_url: https://pkg.go.dev/github.com/prometheus/client_model
153 | version: 0.0.0
154 | license: Apache-2.0
155 | open_source: true
156 | hosted_saas: false
157 | category: Libraries
158 | sub_category: Go Modules Packages
159 | image_url: https://img.stackshare.io/package/go-packages/image.png
160 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
161 | detection_source: go.mod
162 | last_updated_by: Clivern
163 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
164 | - name: cobra
165 | description: A Commander for modern Go CLI interactions
166 | package_url: https://pkg.go.dev/github.com/spf13/cobra
167 | version: 1.7.0
168 | license: Apache-2.0
169 | open_source: true
170 | hosted_saas: false
171 | category: Libraries
172 | sub_category: Go Modules Packages
173 | image_url: https://img.stackshare.io/package/go-packages/image.png
174 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
175 | detection_source: go.mod
176 | last_updated_by: Clivern
177 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
178 | - name: codec
179 | description: Idiomatic codec and rpc lib for msgpack, cbor, json, etc
180 | package_url: https://pkg.go.dev/github.com/ugorji/go/codec
181 | version: 1.2.11
182 | license: MIT
183 | open_source: true
184 | hosted_saas: false
185 | category: Libraries
186 | sub_category: Go Modules Packages
187 | image_url: https://img.stackshare.io/package/go-packages/image.png
188 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
189 | detection_source: go.mod
190 | last_updated_by: Clivern
191 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
192 | - name: common
193 | description: Go libraries shared across Prometheus components and libraries
194 | package_url: https://pkg.go.dev/github.com/prometheus/common
195 | version: 0.42.0
196 | license: Apache-2.0
197 | open_source: true
198 | hosted_saas: false
199 | category: Libraries
200 | sub_category: Go Modules Packages
201 | image_url: https://img.stackshare.io/package/go-packages/image.png
202 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
203 | detection_source: go.mod
204 | last_updated_by: Clivern
205 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
206 | - name: convert
207 | description: BDD Testing Framework for Go
208 | package_url: https://pkg.go.dev/github.com/onsi/ginkgo/ginkgo/convert
209 | license: MIT
210 | open_source: true
211 | hosted_saas: false
212 | category: Libraries
213 | sub_category: Go Modules Packages
214 | image_url: https://img.stackshare.io/package/go-packages/image.png
215 | detection_source: go.mod
216 | last_updated_by: Clivern
217 | last_updated_on: 2019-01-08 22:10:24.000000000 Z
218 | - name: crypto
219 | description: Go supplementary cryptography libraries
220 | package_url: https://pkg.go.dev/golang.org/x/crypto
221 | version: 0.0.0
222 | license: BSD-3-Clause
223 | open_source: true
224 | hosted_saas: false
225 | category: Libraries
226 | sub_category: Go Modules Packages
227 | image_url: https://img.stackshare.io/package/go-packages/image.png
228 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
229 | detection_source: go.mod
230 | last_updated_by: Clivern
231 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
232 | vulnerabilities:
233 | - name: Improper Verification of Cryptographic Signature in golang.org/x/crypto
234 | cve_id: CVE-2020-9283
235 | cve_url: https://github.com/advisories/GHSA-ffhg-7mh4-33c4
236 | detected_date: Aug 22
237 | severity: moderate
238 | first_patched: 0.0.0-20200220183623-bac4c82f6975
239 | - name: fsnotify
240 | description: Cross-platform file system notifications for Go
241 | package_url: https://pkg.go.dev/github.com/fsnotify/fsnotify
242 | version: 1.4.7
243 | license: BSD-3-Clause
244 | open_source: true
245 | hosted_saas: false
246 | category: Libraries
247 | sub_category: Go Modules Packages
248 | image_url: https://img.stackshare.io/package/go-packages/image.png
249 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
250 | detection_source: go.mod
251 | last_updated_by: Clivern
252 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
253 | - name: gin
254 | description: Gin is a HTTP web framework written in Go
255 | package_url: https://pkg.go.dev/github.com/gin-gonic/gin
256 | version: 1.9.1
257 | license: MIT
258 | open_source: true
259 | hosted_saas: false
260 | category: Libraries
261 | sub_category: Go Modules Packages
262 | image_url: https://img.stackshare.io/package/go-packages/image.png
263 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
264 | detection_source: go.mod
265 | last_updated_by: Clivern
266 | last_updated_on: 2019-01-08 22:10:24.000000000 Z
267 | - name: ginkgo
268 | description: BDD Testing Framework for Go
269 | package_url: https://pkg.go.dev/github.com/onsi/ginkgo
270 | version: 1.6.0
271 | license: MIT
272 | open_source: true
273 | hosted_saas: false
274 | category: Libraries
275 | sub_category: Go Modules Packages
276 | image_url: https://img.stackshare.io/package/go-packages/image.png
277 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
278 | detection_source: go.mod
279 | last_updated_by: Clivern
280 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
281 | - name: go-isatty
282 | description: Package isatty implements interface to isatty
283 | package_url: https://pkg.go.dev/github.com/mattn/go-isatty
284 | version: 0.0.19
285 | license: MIT
286 | open_source: true
287 | hosted_saas: false
288 | category: Libraries
289 | sub_category: Go Modules Packages
290 | image_url: https://img.stackshare.io/package/go-packages/image.png
291 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
292 | detection_source: go.mod
293 | last_updated_by: Clivern
294 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
295 | - name: go.uuid
296 | description: UUID package for Go
297 | package_url: https://pkg.go.dev/github.com/satori/go.uuid
298 | version: 1.2.0
299 | license: MIT
300 | open_source: true
301 | hosted_saas: false
302 | category: Libraries
303 | sub_category: Go Modules Packages
304 | image_url: https://img.stackshare.io/package/go-packages/image.png
305 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
306 | detection_source: go.mod
307 | last_updated_by: Clivern
308 | last_updated_on: 2019-03-14 22:28:50.000000000 Z
309 | - name: golang_protobuf_extensions
310 | description: Support for streaming Protocol Buffer messages for the Go language
311 | package_url: https://pkg.go.dev/github.com/matttproud/golang_protobuf_extensions
312 | version: 1.0.4
313 | license: Apache-2.0
314 | open_source: true
315 | hosted_saas: false
316 | category: Libraries
317 | sub_category: Go Modules Packages
318 | image_url: https://img.stackshare.io/package/go-packages/image.png
319 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
320 | detection_source: go.mod
321 | last_updated_by: Clivern
322 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
323 | - name: gomega
324 | description: Ginkgo's Preferred Matcher Library
325 | package_url: https://pkg.go.dev/github.com/onsi/gomega
326 | version: 1.7.1
327 | license: MIT
328 | open_source: true
329 | hosted_saas: false
330 | category: Libraries
331 | sub_category: Go Modules Packages
332 | image_url: https://img.stackshare.io/package/go-packages/image.png
333 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
334 | detection_source: go.mod
335 | last_updated_by: Clivern
336 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
337 | - name: gotenv
338 | description: Load environment variables dynamically in Go
339 | package_url: https://pkg.go.dev/github.com/subosito/gotenv
340 | version: 1.4.2
341 | license: MIT
342 | open_source: true
343 | hosted_saas: false
344 | category: Libraries
345 | sub_category: Go Modules Packages
346 | image_url: https://img.stackshare.io/package/go-packages/image.png
347 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
348 | detection_source: go.mod
349 | last_updated_by: Clivern
350 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
351 | - name: hcl
352 | description: HCL is the HashiCorp configuration language
353 | package_url: https://pkg.go.dev/github.com/hashicorp/hcl
354 | version: 1.0.0
355 | license: MPL-2.0
356 | open_source: true
357 | hosted_saas: false
358 | category: Libraries
359 | sub_category: Go Modules Packages
360 | image_url: https://img.stackshare.io/package/go-packages/image.png
361 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
362 | detection_source: go.mod
363 | last_updated_by: Clivern
364 | last_updated_on: 2019-01-08 22:10:24.000000000 Z
365 | - name: ini.v1
366 | description: Package ini provides INI file read and write functionality in Go
367 | package_url: https://pkg.go.dev/gopkg.in/ini.v1
368 | version: 1.67.0
369 | license: Apache-2.0
370 | open_source: true
371 | hosted_saas: false
372 | category: Libraries
373 | sub_category: Go Modules Packages
374 | image_url: https://img.stackshare.io/package/go-packages/image.png
375 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
376 | detection_source: go.mod
377 | last_updated_by: Clivern
378 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
379 | - name: json-iterator/go
380 | description: A high-performance 100% compatible drop-in replacement of "encoding/json"
381 | package_url: https://pkg.go.dev/github.com/json-iterator/go
382 | version: 1.1.12
383 | license: MIT
384 | open_source: true
385 | hosted_saas: false
386 | category: Libraries
387 | sub_category: Go Modules Packages
388 | image_url: https://img.stackshare.io/package/go-packages/image.png
389 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
390 | detection_source: go.mod
391 | last_updated_by: Clivern
392 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
393 | - name: jwalterweatherman
394 | description: So you always leave a note
395 | package_url: https://pkg.go.dev/github.com/spf13/jwalterweatherman
396 | version: 1.1.0
397 | license: MIT
398 | open_source: true
399 | hosted_saas: false
400 | category: Libraries
401 | sub_category: Go Modules Packages
402 | image_url: https://img.stackshare.io/package/go-packages/image.png
403 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
404 | detection_source: go.mod
405 | last_updated_by: Clivern
406 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
407 | - name: jwt-go
408 | description: Golang implementation of JSON Web Tokens
409 | package_url: https://pkg.go.dev/github.com/dgrijalva/jwt-go
410 | license: MIT
411 | open_source: true
412 | hosted_saas: false
413 | category: Libraries
414 | sub_category: Go Modules Packages
415 | image_url: https://img.stackshare.io/package/go-packages/image.png
416 | detection_source: go.mod
417 | last_updated_by: Clivern
418 | last_updated_on: 2019-03-14 22:28:50.000000000 Z
419 | - name: locales
420 | description: ":earth_americas: a set of locales generated from the CLDR Project
421 | which can be used independently or within an i18n package; these were built for
422 | use with"
423 | package_url: https://pkg.go.dev/github.com/go-playground/locales
424 | version: 0.14.1
425 | license: MIT
426 | open_source: true
427 | hosted_saas: false
428 | category: Libraries
429 | sub_category: Go Modules Packages
430 | image_url: https://img.stackshare.io/package/go-packages/image.png
431 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
432 | detection_source: go.mod
433 | last_updated_by: Clivern
434 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
435 | - name: logrus
436 | description: Structured, pluggable logging for Go
437 | package_url: https://pkg.go.dev/github.com/sirupsen/logrus
438 | version: 1.9.3
439 | license: MIT
440 | open_source: true
441 | hosted_saas: false
442 | category: Libraries
443 | sub_category: Go Modules Packages
444 | image_url: https://img.stackshare.io/package/go-packages/image.png
445 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
446 | detection_source: go.mod
447 | last_updated_by: Clivern
448 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
449 | - name: mapstructure
450 | description: Go library for decoding generic map values into native Go structures
451 | package_url: https://pkg.go.dev/github.com/mitchellh/mapstructure
452 | version: 1.5.0
453 | license: MIT
454 | open_source: true
455 | hosted_saas: false
456 | category: Libraries
457 | sub_category: Go Modules Packages
458 | image_url: https://img.stackshare.io/package/go-packages/image.png
459 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
460 | detection_source: go.mod
461 | last_updated_by: Clivern
462 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
463 | - name: mimetype
464 | description: A golang library for detecting the MIME type and file extension
465 | package_url: https://pkg.go.dev/github.com/gabriel-vasile/mimetype
466 | version: 1.4.2
467 | license: MIT
468 | open_source: true
469 | hosted_saas: false
470 | category: Libraries
471 | sub_category: Go Modules Packages
472 | image_url: https://img.stackshare.io/package/go-packages/image.png
473 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
474 | detection_source: go.mod
475 | last_updated_by: Clivern
476 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
477 | - name: mousetrap
478 | description: Detect starting from Windows explorer
479 | package_url: https://pkg.go.dev/github.com/inconshreveable/mousetrap
480 | version: 1.1.0
481 | license: Apache-2.0
482 | open_source: true
483 | hosted_saas: false
484 | category: Libraries
485 | sub_category: Go Modules Packages
486 | image_url: https://img.stackshare.io/package/go-packages/image.png
487 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
488 | detection_source: go.mod
489 | last_updated_by: Clivern
490 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
491 | - name: net
492 | description: Go supplementary network libraries
493 | package_url: https://pkg.go.dev/golang.org/x/net
494 | version: 0.0.0
495 | license: BSD-3-Clause
496 | open_source: true
497 | hosted_saas: false
498 | category: Libraries
499 | sub_category: Go Modules Packages
500 | image_url: https://img.stackshare.io/package/go-packages/image.png
501 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
502 | detection_source: go.mod
503 | last_updated_by: Clivern
504 | last_updated_on: 2019-01-08 22:10:24.000000000 Z
505 | - name: perks
506 | description: Effective Computation of Things
507 | package_url: https://pkg.go.dev/github.com/beorn7/perks
508 | version: 1.0.1
509 | license: MIT
510 | open_source: true
511 | hosted_saas: false
512 | category: Libraries
513 | sub_category: Go Modules Packages
514 | image_url: https://img.stackshare.io/package/go-packages/image.png
515 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
516 | detection_source: go.mod
517 | last_updated_by: Clivern
518 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
519 | - name: pflag
520 | description: Drop-in replacement for Go's flag package
521 | package_url: https://pkg.go.dev/github.com/spf13/pflag
522 | version: 1.0.5
523 | license: BSD-3-Clause
524 | open_source: true
525 | hosted_saas: false
526 | category: Libraries
527 | sub_category: Go Modules Packages
528 | image_url: https://img.stackshare.io/package/go-packages/image.png
529 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
530 | detection_source: go.mod
531 | last_updated_by: Clivern
532 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
533 | - name: procfs
534 | description: Procfs provides functions to retrieve system
535 | package_url: https://pkg.go.dev/github.com/prometheus/procfs
536 | version: 0.10.1
537 | license: Apache-2.0
538 | open_source: true
539 | hosted_saas: false
540 | category: Libraries
541 | sub_category: Go Modules Packages
542 | image_url: https://img.stackshare.io/package/go-packages/image.png
543 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
544 | detection_source: go.mod
545 | last_updated_by: Clivern
546 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
547 | - name: properties
548 | description: Java properties scanner for Go
549 | package_url: https://pkg.go.dev/github.com/magiconair/properties
550 | version: 1.8.7
551 | license: BSD-2-Clause
552 | open_source: true
553 | hosted_saas: false
554 | category: Libraries
555 | sub_category: Go Modules Packages
556 | image_url: https://img.stackshare.io/package/go-packages/image.png
557 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
558 | detection_source: go.mod
559 | last_updated_by: Clivern
560 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
561 | - name: proto
562 | description: Go support for Google's protocol buffers
563 | package_url: https://pkg.go.dev/github.com/golang/protobuf/proto
564 | license: BSD-3-Clause
565 | open_source: true
566 | hosted_saas: false
567 | category: Libraries
568 | sub_category: Go Modules Packages
569 | image_url: https://img.stackshare.io/package/go-packages/image.png
570 | detection_source: go.mod
571 | last_updated_by: Clivern
572 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
573 | - name: reflect2
574 | description: Reflect api without runtime reflect.Value cost
575 | package_url: https://pkg.go.dev/github.com/modern-go/reflect2
576 | version: 1.0.2
577 | license: Apache-2.0
578 | open_source: true
579 | hosted_saas: false
580 | category: Libraries
581 | sub_category: Go Modules Packages
582 | image_url: https://img.stackshare.io/package/go-packages/image.png
583 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
584 | detection_source: go.mod
585 | last_updated_by: Clivern
586 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
587 | - name: sys
588 | description: Go packages for low-level interaction with the operating system
589 | package_url: https://pkg.go.dev/golang.org/x/sys
590 | version: 0.0.0
591 | license: BSD-3-Clause
592 | open_source: true
593 | hosted_saas: false
594 | category: Libraries
595 | sub_category: Go Modules Packages
596 | image_url: https://img.stackshare.io/package/go-packages/image.png
597 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
598 | detection_source: go.mod
599 | last_updated_by: Clivern
600 | last_updated_on: 2019-01-08 22:10:24.000000000 Z
601 | - name: text
602 | description: Go text processing support
603 | package_url: https://pkg.go.dev/golang.org/x/text
604 | version: 0.0.0
605 | license: BSD-3-Clause
606 | open_source: true
607 | hosted_saas: false
608 | category: Libraries
609 | sub_category: Go Modules Packages
610 | image_url: https://img.stackshare.io/package/go-packages/image.png
611 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
612 | detection_source: go.mod
613 | last_updated_by: Clivern
614 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
615 | - name: universal-translator
616 | description: ":speech_balloon: i18n Translator for Go/Golang using CLDR data + pluralization
617 | rules"
618 | package_url: https://pkg.go.dev/github.com/go-playground/universal-translator
619 | version: 0.18.1
620 | license: MIT
621 | open_source: true
622 | hosted_saas: false
623 | category: Libraries
624 | sub_category: Go Modules Packages
625 | image_url: https://img.stackshare.io/package/go-packages/image.png
626 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
627 | detection_source: go.mod
628 | last_updated_by: Clivern
629 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
630 | - name: viper
631 | description: Go configuration with fangs
632 | package_url: https://pkg.go.dev/github.com/spf13/viper
633 | version: 1.16.0
634 | license: MIT
635 | open_source: true
636 | hosted_saas: false
637 | category: Libraries
638 | sub_category: Go Modules Packages
639 | image_url: https://img.stackshare.io/package/go-packages/image.png
640 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
641 | detection_source: go.mod
642 | last_updated_by: Clivern
643 | last_updated_on: 2023-04-24 21:31:39.000000000 Z
644 | - name: websocket
645 | description: A fast
646 | package_url: https://pkg.go.dev/github.com/gorilla/websocket
647 | version: 1.5.0
648 | license: BSD-3-Clause
649 | open_source: true
650 | hosted_saas: false
651 | category: Libraries
652 | sub_category: Go Modules Packages
653 | image_url: https://img.stackshare.io/package/go-packages/image.png
654 | detection_source_url: https://github.com/Clivern/Beaver/blob/main/go.sum
655 | detection_source: go.mod
656 | last_updated_by: renovate[bot]
657 | last_updated_on: 2022-02-15 19:12:44.000000000 Z
658 |
--------------------------------------------------------------------------------
/web/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clivern/Beaver/bd68afd9d44386b789e46d303f7f4a865ac4cc68/web/static/.gitkeep
--------------------------------------------------------------------------------
/web/static/css/app.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clivern/Beaver/bd68afd9d44386b789e46d303f7f4a865ac4cc68/web/static/css/app.css
--------------------------------------------------------------------------------
/web/static/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clivern/Beaver/bd68afd9d44386b789e46d303f7f4a865ac4cc68/web/static/img/logo.png
--------------------------------------------------------------------------------
/web/static/js/beaver.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Beaver Client
3 | */
4 |
5 | function Socket(url){
6 | ws = new WebSocket(url);
7 | ws.onmessage = function(e) { console.log(e); };
8 | ws.onclose = function(){
9 | // Try to reconnect in 5 seconds
10 | setTimeout(function(){Socket(url)}, 5000);
11 | };
12 | }
--------------------------------------------------------------------------------
/web/template/index.tmpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
{{ .title }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
59 |
60 |
61 |
62 |
63 |

64 |
65 | {{ .title }}
66 |
67 |
A Real Time Messaging Server.
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------