mirror of
https://github.com/foomo/sesamy-go.git
synced 2025-10-16 12:35:43 +00:00
Compare commits
202 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54e0166cab | ||
|
|
596615b287 | ||
|
|
1f968d33b5 | ||
|
|
171b14f620 | ||
|
|
c9aee967cb | ||
|
|
3f0b3fca75 | ||
|
|
5b50febce6 | ||
|
|
fcf4229980 | ||
|
|
0a45237051 | ||
|
|
3af21626d8 | ||
|
|
7884aecdae | ||
|
|
f8a36b73ff | ||
|
|
42b76dc10a | ||
|
|
3dcf81f8f1 | ||
|
|
1bd8089d85 | ||
|
|
dc018262f9 | ||
|
|
256789a9e5 | ||
|
|
6be359cbbb | ||
|
|
e0d941a111 | ||
|
|
410d2c5959 | ||
|
|
3e1c057496 | ||
|
|
69189a891f | ||
|
|
0ef8d2ab7e | ||
|
|
8b3a763010 | ||
|
|
6fc85b0f92 | ||
|
|
074b137958 | ||
|
|
845d93bddd | ||
|
|
07b186d846 | ||
|
|
b2764d77d7 | ||
|
|
2634da8453 | ||
|
|
24e6f55ea9 | ||
|
|
9823bd6eed | ||
|
|
0af53a0141 | ||
|
|
6466f0ef79 | ||
|
|
af1b60c7c5 | ||
|
|
6f300d90d5 | ||
|
|
54545c6e06 | ||
|
|
819f40b501 | ||
|
|
28805c1eb3 | ||
|
|
2317e8c6fd | ||
|
|
2fc77d0c65 | ||
|
|
6534d72815 | ||
|
|
73435d4490 | ||
|
|
b6be99c280 | ||
|
|
19235dbc52 | ||
|
|
f4b1a3c77b | ||
|
|
08579227f8 | ||
|
|
79e6f67409 | ||
|
|
12c8d87600 | ||
|
|
8c170e83ad | ||
|
|
69594d99fa | ||
|
|
79ce1d81e4 | ||
|
|
4900b61684 | ||
|
|
778bb0f199 | ||
|
|
f5d0d00b78 | ||
|
|
d5a849611e | ||
|
|
d1967008a8 | ||
|
|
69fc1ac71d | ||
|
|
603cc9e45b | ||
|
|
bc2e2e0957 | ||
|
|
67a12676eb | ||
|
|
0853b5153d | ||
|
|
ad3a673307 | ||
|
|
6680fbb169 | ||
|
|
478b678a6e | ||
|
|
e801701b58 | ||
|
|
2a125cee30 | ||
|
|
e7f9e2af67 | ||
|
|
28573f3798 | ||
|
|
757a4f63e5 | ||
|
|
9cca227cd3 | ||
|
|
0c1db3cad2 | ||
|
|
9af3ced441 | ||
|
|
84f5a2f76b | ||
|
|
deadd984ad | ||
|
|
3f644f8c52 | ||
|
|
a58dfafaef | ||
|
|
615b57f387 | ||
|
|
fe5b8c9f0f | ||
|
|
2770d4f32f | ||
|
|
c5248e77c2 | ||
|
|
4a7ba15e5f | ||
|
|
0e9c5d04a5 | ||
|
|
1787ffa90f | ||
|
|
f0480f02ea | ||
|
|
25620e06f0 | ||
|
|
92ed44c56e | ||
|
|
d457eca621 | ||
|
|
46d78687b8 | ||
|
|
c975593c5c | ||
|
|
e8e7722960 | ||
|
|
d289d04298 | ||
|
|
e96f667d0e | ||
|
|
dae9b3ee29 | ||
|
|
a95ea0147b | ||
|
|
d8443c212b | ||
|
|
499010ae1c | ||
|
|
beb6082670 | ||
|
|
38715a58b6 | ||
|
|
ee153c3504 | ||
|
|
519267b962 | ||
|
|
c22b610038 | ||
|
|
20e55ad210 | ||
|
|
f6a379429b | ||
|
|
dbc3217726 | ||
|
|
a44d43422a | ||
|
|
9673d35bc3 | ||
|
|
56b9e9b67b | ||
|
|
8a91ffb81b | ||
|
|
d6e52d620d | ||
|
|
0bc536f6db | ||
|
|
adbf3046ef | ||
|
|
5fadcd731c | ||
|
|
fcb18a0799 | ||
|
|
7468cd8508 | ||
|
|
4ebeac6af7 | ||
|
|
7249491e46 | ||
|
|
a44e4b1e4c | ||
|
|
199b141f1f | ||
|
|
0b0a8000aa | ||
|
|
032a5177bb | ||
|
|
fc05804b18 | ||
|
|
bea1de4daa | ||
|
|
45bd571282 | ||
|
|
56ab1ada09 | ||
|
|
8ad1c6cee5 | ||
|
|
cfe639e0e7 | ||
|
|
85dd9bb9fd | ||
|
|
d3ffb1977b | ||
|
|
96f9728f1c | ||
|
|
e184d4d467 | ||
|
|
3b8c2a55ae | ||
|
|
70b8a970c5 | ||
|
|
3a414aba79 | ||
|
|
d88146a2e9 | ||
|
|
e5f48dcad8 | ||
|
|
b2891c12d1 | ||
|
|
9331d10e0d | ||
|
|
f8fcacca2a | ||
|
|
dced802d6a | ||
|
|
598ab3b8fb | ||
|
|
54e2519b4d | ||
|
|
9629047b99 | ||
|
|
3c143f5d60 | ||
|
|
738ddd94b5 | ||
|
|
81d4d84868 | ||
|
|
9d0aa01bb1 | ||
|
|
9c4ec4a25b | ||
|
|
4921daa17e | ||
|
|
ec7166f059 | ||
|
|
31f2ea9f8f | ||
|
|
f021853848 | ||
|
|
3bd292ec56 | ||
|
|
d9cee831e8 | ||
|
|
337824cd85 | ||
|
|
08b4b5fd40 | ||
|
|
8385dbe7df | ||
|
|
8fada67bcd | ||
|
|
9a32adcadb | ||
|
|
a4fa86a8f3 | ||
|
|
5cb6719df0 | ||
|
|
8419d07ae4 | ||
|
|
a7746844d0 | ||
|
|
de0c44a649 | ||
|
|
4ead285d4c | ||
|
|
a0b7aabcfa | ||
|
|
d18231b3b2 | ||
|
|
e9853efff8 | ||
|
|
5a1a2f9ea1 | ||
|
|
df15984d9e | ||
|
|
71fc83a0eb | ||
|
|
453e5159a1 | ||
|
|
ce5bd23d48 | ||
|
|
9117669802 | ||
|
|
64c50a6f57 | ||
|
|
4ed27c190e | ||
|
|
e12d083f85 | ||
|
|
2f4880f505 | ||
|
|
ce0e8e6d9d | ||
|
|
de5f5f0dd0 | ||
|
|
f928679030 | ||
|
|
6e2275fb93 | ||
|
|
6246b38719 | ||
|
|
5125125257 | ||
|
|
391f82e2f0 | ||
|
|
67a90c8a8d | ||
|
|
f8908a6dd1 | ||
|
|
0c0f3be0a2 | ||
|
|
131c7f3f87 | ||
|
|
14ce26b622 | ||
|
|
2c68979504 | ||
|
|
202a816cb2 | ||
|
|
c1221f870e | ||
|
|
30550f16cc | ||
|
|
5cfffd2d63 | ||
|
|
bbf3c33612 | ||
|
|
ba2d68fd99 | ||
|
|
1c4f38e552 | ||
|
|
18754b0f1a | ||
|
|
ffd63f3b42 | ||
|
|
f6488fb269 | ||
|
|
a527a59197 |
55
CODE_OF_CONDUCT.md → .github/CODE_OF_CONDUCT.md
vendored
55
CODE_OF_CONDUCT.md → .github/CODE_OF_CONDUCT.md
vendored
@ -1,3 +1,4 @@
|
||||
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
@ -6,8 +7,8 @@ We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
@ -22,17 +23,17 @@ community include:
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
@ -52,7 +53,7 @@ decisions when appropriate.
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
Examples of representing our community include using an official email address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
@ -60,7 +61,7 @@ representative at an online or offline event.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
info@bestbytes.de.
|
||||
info@bestbytes.com.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
@ -82,15 +83,15 @@ behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
@ -106,23 +107,27 @@ Violating these terms may lead to a permanent ban.
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
21
.github/CONTRIBUTING.md
vendored
Normal file
21
.github/CONTRIBUTING.md
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Contributing
|
||||
|
||||
If you want to submit a pull request to fix a bug or enhance an existing
|
||||
feature, please first open an issue and link to that issue when you
|
||||
submit your pull request.
|
||||
|
||||
If you have any questions about a possible submission, feel free to open
|
||||
an issue too.
|
||||
|
||||
### Pull request process
|
||||
|
||||
1. Fork this repository
|
||||
2. Create a branch in your fork to implement the changes. We recommend using
|
||||
the issue number as part of your branch name, e.g. `1234-fixes`
|
||||
3. Ensure that any documentation is updated with the changes that are required
|
||||
by your fix.
|
||||
4. Ensure that any samples are updated if the base image has been changed.
|
||||
5. Submit the pull request. *Do not leave the pull request blank*. Explain exactly
|
||||
what your changes are meant to do and provide simple steps on how to validate
|
||||
your changes. Ensure that you reference the issue you created as well.
|
||||
The pull request will be review before it is merged.
|
||||
17
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report a bug you encountered
|
||||
labels: bug
|
||||
---
|
||||
**What happened**:
|
||||
|
||||
**What you expected to happen**:
|
||||
|
||||
**How to reproduce it (as minimally and precisely as possible)**:
|
||||
|
||||
**Anything else we need to know?**:
|
||||
|
||||
**Environment**:
|
||||
- Affected Version:
|
||||
- OS (e.g: `cat /etc/os-release`):
|
||||
- Others:
|
||||
8
.github/ISSUE_TEMPLATE/enhancement.md
vendored
Normal file
8
.github/ISSUE_TEMPLATE/enhancement.md
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
name: Enhancement Request
|
||||
about: Suggest an enhancement
|
||||
labels: enhancement
|
||||
---
|
||||
**What would you like to be added**:
|
||||
|
||||
**Why is this needed**:
|
||||
19
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
19
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
### Type of Change
|
||||
- [ ] New feature
|
||||
- [ ] Bug fix
|
||||
- [ ] Documentation update
|
||||
- [ ] Refactoring
|
||||
- [ ] Hotfix
|
||||
- [ ] Security patch
|
||||
|
||||
### Description
|
||||
_[Provide a detailed explanation of the changes you have made. Include the reasons behind these changes and any relevant context. Link any related issues.]_
|
||||
|
||||
### Related Issues
|
||||
_[If this pull request addresses an issue, please link to it here (e.g., Fixes #123).]_
|
||||
|
||||
### Checklist
|
||||
- [ ] My code adheres to the coding and style guidelines of the project.
|
||||
- [ ] I have performed a self-review of my own code.
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas.
|
||||
- [ ] I have made corresponding changes to the documentation.
|
||||
45
.github/SECURITY.md
vendored
Normal file
45
.github/SECURITY.md
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
# Security Guidelines
|
||||
|
||||
## How security is managed on this project
|
||||
|
||||
The foomo team and community take security seriously and wants to ensure that
|
||||
we maintain a secure environment and provide secure solutions for the open
|
||||
source community. To help us achieve these goals, please note the
|
||||
following before using this software:
|
||||
|
||||
- Review the software license to understand the contributor's obligations in
|
||||
terms of warranties and suitability for purpose
|
||||
- For any questions or concerns about security, you can
|
||||
[create an issue][new-issue] or [report a vulnerability][new-sec-issue]
|
||||
- We request that you work with our security team and opt for
|
||||
responsible disclosure using the guidelines below
|
||||
- All security related issues and pull requests you make should be tagged with
|
||||
"security" for easy identification
|
||||
- Please monitor this repository and update your environment in a timely manner
|
||||
as we release patches and updates
|
||||
|
||||
## Responsibly Disclosing Security Bugs
|
||||
|
||||
If you find a security bug in this repository, please work with contributors
|
||||
following responsible disclosure principles and these guidelines:
|
||||
|
||||
- Do not submit a normal issue or pull request in our public repository, instead
|
||||
[report it directly][new-sec-issue].
|
||||
- We will review your submission and may follow up for additional details
|
||||
- If you have a patch, we will review it and approve it privately; once approved
|
||||
for release you can submit it as a pull request publicly in the repository (we
|
||||
give credit where credit is due)
|
||||
- We will keep you informed during our investigation, feel free to check in for
|
||||
a status update
|
||||
- We will release the fix and publicly disclose the issue as soon as possible,
|
||||
but want to ensure we due properly due diligence before releasing
|
||||
- Please do not publicly blog or post about the security issue until after we
|
||||
have updated the public repo so that other downstream users have an opportunity
|
||||
to patch
|
||||
|
||||
## Contact / Misc
|
||||
|
||||
If you have any questions, please reach out directly by [creating an issue][new-issue].
|
||||
|
||||
[new-issue]: https://github.com/foomo/sesamy-go/issues/new/choose
|
||||
[new-sec-issue]: https://github.com/foomo/sesamy-go/security/advisories/new
|
||||
BIN
.github/assets/sesamy.png
vendored
Normal file
BIN
.github/assets/sesamy.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 177 KiB |
32
.github/dependabot.yml
vendored
32
.github/dependabot.yml
vendored
@ -1,15 +1,25 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
- package-ecosystem: 'github-actions'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
day: 'sunday'
|
||||
interval: 'weekly'
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- '*'
|
||||
|
||||
- package-ecosystem: 'gomod'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: 'sunday'
|
||||
interval: 'weekly'
|
||||
groups:
|
||||
gomod-security:
|
||||
applies-to: security-updates
|
||||
patterns: ['*']
|
||||
gomod-update:
|
||||
applies-to: version-updates
|
||||
patterns: ['*']
|
||||
|
||||
7
.github/workflows/release.yml
vendored
7
.github/workflows/release.yml
vendored
@ -17,13 +17,12 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- run: git fetch --force --tags
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
check-latest: true
|
||||
go-version-file: go.mod
|
||||
|
||||
- uses: goreleaser/goreleaser-action@v5
|
||||
- uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
version: latest
|
||||
args: release --clean
|
||||
|
||||
9
.github/workflows/test.yml
vendored
9
.github/workflows/test.yml
vendored
@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
merge_group:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
@ -18,18 +19,20 @@ jobs:
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
check-latest: true
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
- uses: gotesttools/gotestfmt-action@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: golangci/golangci-lint-action@v4
|
||||
- uses: golangci/golangci-lint-action@v8
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout=5m
|
||||
|
||||
- run: make test
|
||||
- name: Run tests
|
||||
run: GO_TEST_TAGS=-skip go test -coverprofile=coverage.out -race -json ./... | gotestfmt
|
||||
|
||||
- uses: coverallsapp/github-action@v2
|
||||
with:
|
||||
|
||||
26
.gitignore
vendored
26
.gitignore
vendored
@ -1,13 +1,25 @@
|
||||
.*
|
||||
*.log
|
||||
*.zip
|
||||
*.tar
|
||||
*.out
|
||||
*.log
|
||||
/bin/
|
||||
/tmp/
|
||||
!.github/
|
||||
!.husky/
|
||||
!.editorconfig
|
||||
|
||||
## Git
|
||||
!.gitkeep
|
||||
!.gitignore
|
||||
|
||||
## GitHub
|
||||
!.github/
|
||||
|
||||
## Editorconfig
|
||||
!.editorconfig
|
||||
|
||||
## Husky
|
||||
!.husky/
|
||||
!.husky.yaml
|
||||
|
||||
## Golang
|
||||
!.golangci.yml
|
||||
!.goreleaser.yml
|
||||
!.husky.yaml
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
264
.golangci.yml
264
.golangci.yml
@ -1,89 +1,215 @@
|
||||
version: "2"
|
||||
run:
|
||||
skip-dirs:
|
||||
- tmp
|
||||
|
||||
linters-settings:
|
||||
# https://golangci-lint.run/usage/linters/#revive
|
||||
revive:
|
||||
rules:
|
||||
- name: indent-error-flow
|
||||
disabled: true
|
||||
- name: exported
|
||||
disabled: true
|
||||
# https://golangci-lint.run/usage/linters/#gocritic
|
||||
gocritic:
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- performance
|
||||
- style
|
||||
disabled-tags:
|
||||
- experimental
|
||||
- opinionated
|
||||
disabled-checks:
|
||||
- ifElseChain
|
||||
settings:
|
||||
hugeParam:
|
||||
sizeThreshold: 512
|
||||
# https://golangci-lint.run/usage/linters/#gosec
|
||||
gosec:
|
||||
config:
|
||||
G306: "0700"
|
||||
excludes:
|
||||
- G101 # Potential hardcoded credentials
|
||||
- G102 # Bind to all interfaces
|
||||
- G112 # Potential slowloris attack
|
||||
- G401 # Detect the usage of DES, RC4, MD5 or SHA1
|
||||
- G402 # Look for bad TLS connection settings
|
||||
- G404 # Insecure random number source (rand)
|
||||
- G501 # Import blocklist: crypto/md5
|
||||
- G505 # Import blocklist: crypto/sha1
|
||||
|
||||
go: 1.24.1
|
||||
build-tags: [ safe ]
|
||||
modules-download-mode: readonly
|
||||
linters:
|
||||
default: none
|
||||
enable:
|
||||
# Enabled by default linters:
|
||||
- errcheck
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
## Default linters
|
||||
- errcheck # errcheck is a program for checking for unchecked errors in Go code. These unchecked errors can be critical bugs in some cases [fast: false, auto-fix: false]
|
||||
- govet # (vet, vetshadow) Vet examines Go source code and reports suspicious constructs. It is roughly the same as 'go vet' and uses its passes. [fast: false, auto-fix: false]
|
||||
- ineffassign # Detects when assignments to existing variables are not used [fast: true, auto-fix: false]
|
||||
- staticcheck # (megacheck) It's a set of rules from staticcheck. It's not the same thing as the staticcheck binary. The author of staticcheck doesn't support or approve the use of staticcheck as a library inside golangci-lint. [fast: false, auto-fix: false]
|
||||
- unused # (megacheck) Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
|
||||
|
||||
# Disabled by default linters:
|
||||
- asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers [fast: true, auto-fix: false]
|
||||
## Recommended linters
|
||||
- asasalint # check for pass []any as any in variadic func(...any) [fast: false, auto-fix: false]
|
||||
- asciicheck # checks that all code identifiers does not have non-ASCII symbols in the name [fast: true, auto-fix: false]
|
||||
- bidichk # Checks for dangerous unicode character sequences [fast: true, auto-fix: false]
|
||||
#- dupl # Tool for code clone detection [fast: true, auto-fix: false]
|
||||
- bodyclose # checks whether HTTP response body is closed successfully [fast: false, auto-fix: false]
|
||||
- canonicalheader # checks whether net/http.Header uses canonical header [fast: false, auto-fix: false]
|
||||
- containedctx # containedctx is a linter that detects struct contained context.Context field [fast: false, auto-fix: false]
|
||||
- contextcheck # check whether the function uses a non-inherited context [fast: false, auto-fix: false]
|
||||
- copyloopvar # (go >= 1.22) copyloopvar is a linter detects places where loop variables are copied [fast: true, auto-fix: false]
|
||||
- decorder # check declaration order and count of types, constants, variables and functions [fast: true, auto-fix: false]
|
||||
- durationcheck # check for two durations multiplied together [fast: false, auto-fix: false]
|
||||
- errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and reports occations, where the check for the returned error can be omitted. [fast: false, auto-fix: false]
|
||||
- errname # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`. [fast: false, auto-fix: false]
|
||||
- errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. [fast: false, auto-fix: false]
|
||||
- exhaustive # check exhaustiveness of enum switch statements [fast: false, auto-fix: false]
|
||||
- exptostd # Detects functions from golang.org/x/exp/ that can be replaced by std functions. [auto-fix]
|
||||
- fatcontext # detects nested contexts in loops and function literals [fast: false, auto-fix: false]
|
||||
#- forbidigo # Forbids identifiers [fast: false, auto-fix: false]
|
||||
- forcetypeassert # finds forced type assertions [fast: true, auto-fix: false]
|
||||
#- gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false]
|
||||
- gocheckcompilerdirectives # Checks that go compiler directive comments (//go:) are valid. [fast: true, auto-fix: false]
|
||||
- gochecksumtype # Run exhaustiveness checks on Go "sum types" [fast: false, auto-fix: false]
|
||||
- goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]
|
||||
- gocritic # Provides diagnostics that check for bugs, performance and style issues. [fast: false, auto-fix: false]
|
||||
- goimports # In addition to fixing imports, goimports also formats your code in the same style as gofmt. [fast: true, auto-fix: true]
|
||||
#- gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. [fast: true, auto-fix: false]
|
||||
- gocritic # Provides diagnostics that check for bugs, performance and style issues. [fast: false, auto-fix: true]
|
||||
- goheader # Checks is file header matches to pattern [fast: true, auto-fix: true]
|
||||
- gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. [fast: true, auto-fix: false]
|
||||
- gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. [fast: true, auto-fix: false]
|
||||
- goprintffuncname # Checks that printf-like functions are named with `f` at the end [fast: true, auto-fix: false]
|
||||
- gosec # (gas): Inspects source code for security problems [fast: false, auto-fix: false]
|
||||
- grouper # An analyzer to analyze expression groups. [fast: true, auto-fix: false]
|
||||
- goprintffuncname # Checks that printf-like functions are named with `f` at the end. [fast: true, auto-fix: false]
|
||||
- gosec # (gas) Inspects source code for security problems [fast: false, auto-fix: false]
|
||||
- gosmopolitan # Report certain i18n/l10n anti-patterns in your Go codebase [fast: false, auto-fix: false]
|
||||
- grouper # Analyze expression groups. [fast: true, auto-fix: false]
|
||||
- iface # Detect the incorrect use of interfaces, helping developers avoid interface pollution. [fast: false, auto-fix: false]
|
||||
- importas # Enforces consistent import aliases [fast: false, auto-fix: false]
|
||||
#- maintidx # maintidx measures the maintainability index of each function. [fast: true, auto-fix: false]
|
||||
- inamedparam # reports interfaces with unnamed method parameters [fast: true, auto-fix: false]
|
||||
- loggercheck # (logrlint) Checks key value pairs for common logger libraries (kitlog,klog,logr,zap). [fast: false, auto-fix: false]
|
||||
- makezero # Finds slice declarations with non-zero initial length [fast: false, auto-fix: false]
|
||||
- misspell # Finds commonly misspelled English words in comments [fast: true, auto-fix: true]
|
||||
- nakedret # Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false]
|
||||
#- nestif # Reports deeply nested if statements [fast: true, auto-fix: false]
|
||||
- mirror # reports wrong mirror patterns of bytes/strings usage [fast: false, auto-fix: true]
|
||||
- misspell # Finds commonly misspelled English words [fast: true, auto-fix: true]
|
||||
- musttag # enforce field tags in (un)marshaled structs [fast: false, auto-fix: false]
|
||||
- nakedret # Checks that functions with naked returns are not longer than a maximum size (can be zero). [fast: true, auto-fix: false]
|
||||
- nilerr # Finds the code that returns nil even if it checks that the error is not nil. [fast: false, auto-fix: false]
|
||||
- nilnesserr # Reports constructs that checks for err != nil, but returns a different nil value error.
|
||||
- nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value. [fast: false, auto-fix: false]
|
||||
- noctx # noctx finds sending http request without context.Context [fast: false, auto-fix: false]
|
||||
- nolintlint # Reports ill-formed or insufficient nolint directives [fast: true, auto-fix: false]
|
||||
- noctx # Finds sending http request without context.Context [fast: false, auto-fix: false]
|
||||
- nolintlint # Reports ill-formed or insufficient nolint directives [fast: true, auto-fix: true]
|
||||
- nonamedreturns # Reports all named returns [fast: false, auto-fix: false]
|
||||
- nosprintfhostport # Checks for misuse of Sprintf to construct a host with port in a URL. [fast: true, auto-fix: false]
|
||||
- prealloc # Finds slice declarations that could potentially be pre-allocated [fast: true, auto-fix: false]
|
||||
- paralleltest # Detects missing usage of t.Parallel() method in your Go test [fast: false, auto-fix: false]
|
||||
- predeclared # find code that shadows one of Go's predeclared identifiers [fast: true, auto-fix: false]
|
||||
- promlinter # Check Prometheus metrics naming via promlint [fast: true, auto-fix: false]
|
||||
- reassign # Checks that package variables are not reassigned [fast: false, auto-fix: false]
|
||||
- recvcheck # checks for receiver type consistency [fast: false, auto-fix: false]
|
||||
- revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. [fast: false, auto-fix: false]
|
||||
#- tagliatelle # Checks the struct tags. [fast: true, auto-fix: false]
|
||||
- rowserrcheck # checks whether Rows.Err of rows is checked successfully [fast: false, auto-fix: false]
|
||||
- spancheck # Checks for mistakes with OpenTelemetry/Census spans. [fast: false, auto-fix: false]
|
||||
- sqlclosecheck # Checks that sql.Rows, sql.Stmt, sqlx.NamedStmt, pgx.Query are closed. [fast: false, auto-fix: false]
|
||||
- testableexamples # linter checks if examples are testable (have an expected output) [fast: true, auto-fix: false]
|
||||
- testifylint # Checks usage of github.com/stretchr/testify. [fast: false, auto-fix: false]
|
||||
- testpackage # linter that makes you use a separate _test package [fast: true, auto-fix: false]
|
||||
- thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers [fast: false, auto-fix: false]
|
||||
- thelper # thelper detects tests helpers which is not start with t.Helper() method. [fast: false, auto-fix: false]
|
||||
- tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes. [fast: false, auto-fix: false]
|
||||
- unconvert # Remove unnecessary type conversions [fast: false, auto-fix: false]
|
||||
- unparam # Reports unused function parameters [fast: false, auto-fix: false]
|
||||
- usestdlibvars # A linter that detect the possibility to use variables/constants from the Go standard library. [fast: true, auto-fix: false]
|
||||
- wastedassign # wastedassign finds wasted assignment statements. [fast: false, auto-fix: false]
|
||||
- whitespace # Tool for detection of leading and trailing whitespace [fast: true, auto-fix: true]
|
||||
disable:
|
||||
- unused
|
||||
- usetesting # Reports uses of functions with replacement inside the testing package. [auto-fix]
|
||||
- wastedassign # Finds wasted assignment statements [fast: false, auto-fix: false]
|
||||
- whitespace # Whitespace is a linter that checks for unnecessary newlines at the start and end of functions, if, for, etc. [fast: true, auto-fix: true]
|
||||
|
||||
## Discouraged linters
|
||||
#- cyclop # checks function and package cyclomatic complexity [fast: false, auto-fix: false]
|
||||
#- depguard # Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false]
|
||||
#- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
|
||||
#- dupl # Tool for code clone detection [fast: true, auto-fix: false]
|
||||
#- dupword # checks for duplicate words in the source code [fast: true, auto-fix: false]
|
||||
#- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
|
||||
#- err113 # Go linter to check the errors handling expressions [fast: false, auto-fix: false]
|
||||
#- exhaustruct # Checks if all structure fields are initialized [fast: false, auto-fix: false]
|
||||
#- funlen # Tool for detection of long functions [fast: true, auto-fix: false]
|
||||
#- ginkgolinter # enforces standards of using ginkgo and gomega [fast: false, auto-fix: false]
|
||||
#- gochecknoglobals # Check that no global variables exist. [fast: false, auto-fix: false]
|
||||
#- gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false]
|
||||
#- gocognit # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false]
|
||||
#- gocyclo # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false]
|
||||
#- godot # Check if comments end in a period [fast: true, auto-fix: true]
|
||||
#- godox # Tool for detection of comment keywords [fast: true, auto-fix: false]
|
||||
#- interfacebloat # A linter that checks the number of methods inside an interface. [fast: true, auto-fix: false]
|
||||
#- intrange # (go >= 1.22) intrange is a linter to find places where for loops could make use of an integer range. [fast: true, auto-fix: false]
|
||||
#- ireturn # Accept Interfaces, Return Concrete Types [fast: false, auto-fix: false]
|
||||
#- lll # Reports long lines [fast: true, auto-fix: false]
|
||||
#- maintidx # maintidx measures the maintainability index of each function. [fast: true, auto-fix: false]
|
||||
#- nestif # Reports deeply nested if statements [fast: true, auto-fix: false]
|
||||
#- nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity [fast: true, auto-fix: false]
|
||||
#- mnd # An analyzer to detect magic numbers. [fast: true, auto-fix: false]
|
||||
#- perfsprint # Checks that fmt.Sprintf can be replaced with a faster alternative. [fast: false, auto-fix: false]
|
||||
#- prealloc # Finds slice declarations that could potentially be pre-allocated [fast: true, auto-fix: false]
|
||||
#- protogetter # Reports direct reads from proto message fields when getters should be used [fast: false, auto-fix: true]
|
||||
#- sloglint # ensure consistent code style when using log/slog [fast: false, auto-fix: false]
|
||||
#- tagalign # check that struct tags are well aligned [fast: true, auto-fix: true]
|
||||
#- tagliatelle # Checks the struct tags. [fast: true, auto-fix: false]
|
||||
#- unparam # Reports unused function parameters [fast: false, auto-fix: false]
|
||||
#- varnamelen # checks that the length of a variable's name matches its scope [fast: false, auto-fix: false]
|
||||
#- wrapcheck # Checks that errors returned from external packages are wrapped [fast: false, auto-fix: false]
|
||||
#- wsl # add or remove empty lines [fast: true, auto-fix: false]
|
||||
#- zerologlint # Detects the wrong usage of `zerolog` that a user forgets to dispatch with `Send` or `Msg` [fast: false, auto-fix: false]
|
||||
settings:
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: true
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- ifElseChain
|
||||
- commentFormatting
|
||||
gomoddirectives:
|
||||
replace-local: true
|
||||
gosec:
|
||||
confidence: medium
|
||||
importas:
|
||||
no-unaliased: true
|
||||
misspell:
|
||||
mode: restricted
|
||||
predeclared:
|
||||
ignore:
|
||||
- new
|
||||
- error
|
||||
revive:
|
||||
enable-all-rules: true
|
||||
rules:
|
||||
- name: line-length-limit
|
||||
disabled: true
|
||||
- name: cognitive-complexity
|
||||
disabled: true
|
||||
- name: unused-parameter
|
||||
disabled: true
|
||||
- name: add-constant
|
||||
disabled: true
|
||||
- name: cyclomatic
|
||||
disabled: true
|
||||
- name: function-length
|
||||
disabled: true
|
||||
- name: function-result-limit
|
||||
disabled: true
|
||||
- name: flag-parameter
|
||||
disabled: true
|
||||
- name: unused-receiver
|
||||
disabled: true
|
||||
- name: argument-limit
|
||||
disabled: true
|
||||
- name: max-control-nesting
|
||||
disabled: true
|
||||
- name: comment-spacings
|
||||
disabled: true
|
||||
- name: max-public-structs
|
||||
disabled: true
|
||||
- name: struct-tag
|
||||
arguments:
|
||||
- json,inline
|
||||
- name: unhandled-error
|
||||
arguments:
|
||||
- fmt.Println
|
||||
- io.Writer.Write
|
||||
- strings.Builder.WriteString
|
||||
- name: deep-exit
|
||||
disabled: true
|
||||
- name: if-return
|
||||
disabled: true
|
||||
- name: empty-block
|
||||
disabled: true
|
||||
- name: indent-error-flow
|
||||
disabled: true
|
||||
testifylint:
|
||||
disable:
|
||||
- float-compare
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
rules:
|
||||
- linters:
|
||||
- forbidigo
|
||||
- forcetypeassert
|
||||
path: _test\.go
|
||||
- linters:
|
||||
- asasalint
|
||||
path: (.+)_test\.go
|
||||
paths:
|
||||
- tmp
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- goimports
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- tmp
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
version: 2
|
||||
|
||||
builds:
|
||||
- skip: true
|
||||
|
||||
release:
|
||||
prerelease: auto
|
||||
|
||||
changelog:
|
||||
use: github-native
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
hooks:
|
||||
pre-commit:
|
||||
- golangci-lint run --fast
|
||||
- golangci-lint run --fast-only
|
||||
- husky lint-staged
|
||||
commit-msg:
|
||||
# only execute if not in a merge
|
||||
@ -8,7 +8,7 @@ hooks:
|
||||
|
||||
lint-staged:
|
||||
'*.go':
|
||||
- goimports -l -w
|
||||
- golangci-lint fmt
|
||||
|
||||
lint-commit:
|
||||
types: '^(feat|fix|build|chore|docs|perf|refactor|revert|style|test|wip)$'
|
||||
|
||||
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Foomo web framework
|
||||
Copyright (c) foomo by bestbytes
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
5
Makefile
5
Makefile
@ -21,13 +21,14 @@
|
||||
.PHONY: doc
|
||||
## Run tests
|
||||
doc:
|
||||
@open "http://localhost:6060/pkg/github.com/foomo/keel/"
|
||||
@open "http://localhost:6060/pkg/github.com/foomo/sesamy-go/"
|
||||
@godoc -http=localhost:6060 -play
|
||||
|
||||
.PHONY: test
|
||||
## Run tests
|
||||
test:
|
||||
@go test -coverprofile=coverage.out -race -json ./... | gotestfmt
|
||||
@GO_TEST_TAGS=-skip go test -coverprofile=coverage.out -race ./...
|
||||
#@GO_TEST_TAGS=-skip go test -coverprofile=coverage.out -race -json ./... | gotestfmt
|
||||
|
||||
.PHONY: lint
|
||||
## Run linter
|
||||
|
||||
17
README.md
17
README.md
@ -1,15 +1,26 @@
|
||||
# Sesamy Go SDK
|
||||
|
||||
[](https://github.com/foomo/sesamy-go/actions/workflows/test.yml)
|
||||
[](https://goreportcard.com/report/github.com/foomo/sesamy-go)
|
||||
[](https://godoc.org/github.com/foomo/sesamy-go)
|
||||
|
||||
<p align="center">
|
||||
<img alt="sesamy" src=".github/assets/sesamy.png"/>
|
||||
</p>
|
||||
|
||||
# Sesamy Go SDK
|
||||
|
||||
> **Se**rver **S**ide T**a**g **M**anagement **S**ystem
|
||||
|
||||
|
||||
## References
|
||||
|
||||
- [Event naming rules](https://support.google.com/analytics/answer/13316687)
|
||||
|
||||
## How to Contribute
|
||||
|
||||
Make a pull request...
|
||||
Please refer to the [CONTRIBUTING](.github/CONTRIBUTING.md) details and follow the [CODE_OF_CONDUCT](.github/CODE_OF_CONDUCT.md) and [SECURITY](.github/SECURITY.md) guidelines.
|
||||
|
||||
## License
|
||||
|
||||
Distributed under MIT License, please see license file within the code for more details.
|
||||
|
||||
_Made with ♥ [foomo](https://www.foomo.org) by [bestbytes](https://www.bestbytes.com)_
|
||||
|
||||
172
go.mod
172
go.mod
@ -1,35 +1,161 @@
|
||||
module github.com/foomo/sesamy-go
|
||||
|
||||
go 1.21
|
||||
go 1.24.1
|
||||
|
||||
require (
|
||||
github.com/ThreeDotsLabs/watermill v1.3.5
|
||||
github.com/foomo/keel v0.17.3
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/foomo/go v0.0.3
|
||||
github.com/foomo/gostandards v0.2.0
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/golang/snappy v1.0.0
|
||||
github.com/grafana/dskit v0.0.0-20250605175458-8a9c353edab7
|
||||
github.com/grafana/loki/pkg/push v0.0.0-20250606045814-5db2927512ec
|
||||
github.com/grafana/loki/v3 v3.5.1
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/prometheus/common v0.64.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
go.opentelemetry.io/otel/trace v1.36.0
|
||||
go.uber.org/zap v1.27.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/avast/retry-go v3.0.0+incompatible // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fbiville/markdown-table-formatter v0.3.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.2 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
|
||||
github.com/armon/go-metrics v0.4.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dennwc/varint v1.0.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/edsrzf/mmap-go v1.2.0 // indirect
|
||||
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/go-kit/log v0.2.1 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/philhofer/fwd v1.1.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/sony/gobreaker v0.5.0 // indirect
|
||||
github.com/tinylib/msgp v1.1.8 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.32.0 // indirect
|
||||
go.opentelemetry.io/otel v1.7.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v0.30.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
golang.org/x/crypto v0.12.0 // indirect
|
||||
github.com/go-redsync/redsync/v4 v4.13.0 // indirect
|
||||
github.com/gogo/googleapis v1.4.1 // indirect
|
||||
github.com/gogo/status v1.1.1 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d // indirect
|
||||
github.com/grafana/jsonparser v0.0.0-20241004153430-023329977675 // indirect
|
||||
github.com/grafana/otel-profiling-go v0.5.1 // indirect
|
||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect
|
||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
|
||||
github.com/hashicorp/consul/api v1.31.2 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-hclog v1.6.3 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-metrics v0.5.4 // indirect
|
||||
github.com/hashicorp/go-msgpack/v2 v2.1.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
|
||||
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
||||
github.com/hashicorp/memberlist v0.5.3 // indirect
|
||||
github.com/hashicorp/serf v0.10.2 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/jaegertracing/jaeger-idl v0.5.0 // indirect
|
||||
github.com/jpillora/backoff v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mdlayher/socket v0.5.1 // indirect
|
||||
github.com/mdlayher/vsock v1.2.1 // indirect
|
||||
github.com/miekg/dns v1.1.63 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
|
||||
github.com/opentracing-contrib/go-grpc v0.1.1 // indirect
|
||||
github.com/opentracing-contrib/go-stdlib v1.1.0 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
|
||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.22.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/exporter-toolkit v0.14.0 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/prometheus/prometheus v0.303.1 // indirect
|
||||
github.com/redis/go-redis/v9 v9.8.0 // indirect
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
|
||||
github.com/sercand/kuberesolver/v6 v6.0.0 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/sony/gobreaker/v2 v2.1.0 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/tjhop/slog-gokit v0.1.4 // indirect
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.4 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.4 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.4 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/collector/pdata v1.28.1 // indirect
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.61.0 // indirect
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.61.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.35.0 // indirect
|
||||
go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0 // indirect
|
||||
go.opentelemetry.io/otel v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.58.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/log v0.12.2 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/log v0.12.2 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.6.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect
|
||||
golang.org/x/crypto v0.39.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
|
||||
golang.org/x/mod v0.25.0 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.15.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/grpc v1.73.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
736
go.sum
736
go.sum
@ -1,102 +1,708 @@
|
||||
github.com/ThreeDotsLabs/watermill v1.3.5 h1:50JEPEhMGZQMh08ct0tfO1PsgMOAOhV3zxK2WofkbXg=
|
||||
github.com/ThreeDotsLabs/watermill v1.3.5/go.mod h1:O/u/Ptyrk5MPTxSeWM5vzTtZcZfxXfO9PK9eXTYiFZY=
|
||||
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
|
||||
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.119.0 h1:tw7OjErMzJKbbjaEHkrt60KQrK5Wus/boCZ7tm5/RNE=
|
||||
cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
|
||||
cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
|
||||
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3XowWfhZRUpnfC+rGZMEVoSiji+b+/HFAPU4=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM=
|
||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
|
||||
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
||||
github.com/Workiva/go-datastructures v1.1.5 h1:5YfhQ4ry7bZc2Mc7R0YZyYwpf5c6t1cEFvdAhd6Mkf4=
|
||||
github.com/Workiva/go-datastructures v1.1.5/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
|
||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0=
|
||||
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=
|
||||
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
|
||||
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI=
|
||||
github.com/alicebob/miniredis/v2 v2.34.0 h1:mBFWMaJSNL9RwdGRyEDoAAv8OQc5UlEhLDQggTglU/0=
|
||||
github.com/alicebob/miniredis/v2 v2.34.0/go.mod h1:kWShP4b58T1CW0Y5dViCd5ztzrDqRWqM3nksiyXk5s8=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
|
||||
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
|
||||
github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk=
|
||||
github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3 h1:6df1vn4bBlDDo4tARvBm7l6KA9iVMnE3NWizDeWSrps=
|
||||
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3/go.mod h1:CIWtjkly68+yqLPbvwwR/fjNJA/idrtULjZWh2v1ys0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 h1:6lhrsTEnloDPXyeZBvSYvQf8u86jbKehZPVDDlkgDl4=
|
||||
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
|
||||
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
|
||||
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fbiville/markdown-table-formatter v0.3.0 h1:PIm1UNgJrFs8q1htGTw+wnnNYvwXQMMMIKNZop2SSho=
|
||||
github.com/fbiville/markdown-table-formatter v0.3.0/go.mod h1:q89TDtSEVDdTaufgSbfHpNVdPU/bmfvqNkrC5HagmLY=
|
||||
github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o=
|
||||
github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/foomo/keel v0.17.3 h1:ldsTk4ANhiFgtEAYUlacSXqfisX2UkUvuFVy4FF+o0g=
|
||||
github.com/foomo/keel v0.17.3/go.mod h1:lwlWZ5ZvuX0PCxliabY69AmxQZnU9nIc10wWuavvKtM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE=
|
||||
github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84=
|
||||
github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:IT4JYU7k4ikYg1SCxNI1/Tieq/NFvh6dzLdgi7eu0tM=
|
||||
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/foomo/go v0.0.3 h1:5pGzcPC78dImuBTT7nsZZnH+GIQUylbCtMkFEH26uZk=
|
||||
github.com/foomo/go v0.0.3/go.mod h1:x6g64wiQusqaFElnh5rlk9unCgLKmfUWy0YFLejJxio=
|
||||
github.com/foomo/gostandards v0.2.0 h1:Ryd7TI9yV3Xk5B84DcUDB7KcL3LzQ8NS+TVOrFxTYfA=
|
||||
github.com/foomo/gostandards v0.2.0/go.mod h1:XQx7Ur6vyvxaIe2cQvAthuhPYDe+d2soibqVcXDXOh4=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
|
||||
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
|
||||
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOrTI=
|
||||
github.com/go-redis/redis/v7 v7.4.1/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/go-redsync/redsync/v4 v4.13.0 h1:49X6GJfnbLGaIpBBREM/zA4uIMDXKAh1NDkvQ1EkZKA=
|
||||
github.com/go-redsync/redsync/v4 v4.13.0/go.mod h1:HMW4Q224GZQz6x1Xc7040Yfgacukdzu7ifTDAKiyErQ=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||
github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0=
|
||||
github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/gogo/status v1.1.1 h1:DuHXlSFHNKqTQ+/ACf5Vs6r4X/dH2EgIzR9Vr+H65kg=
|
||||
github.com/gogo/status v1.1.1/go.mod h1:jpG3dM5QPcqu19Hg8lkUhBFBa3TcLs1DG7+2Jqci7oU=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
|
||||
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
|
||||
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/grafana/dskit v0.0.0-20250605175458-8a9c353edab7 h1:O9Evoqc8/4KaWrMshUG23P417UOjvwIee9jRe5JzHAs=
|
||||
github.com/grafana/dskit v0.0.0-20250605175458-8a9c353edab7/go.mod h1:OiN4P4aC6LwLzLbEupH3Ue83VfQoNMfG48rsna8jI/E=
|
||||
github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d h1:oXRJlb9UjVsl6LhqBdbyAQ9YFhExwsj4bjh5vwMNRZY=
|
||||
github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d/go.mod h1:j/s0jkda4UXTemDs7Pgw/vMT06alWc42CHisvYac0qw=
|
||||
github.com/grafana/jsonparser v0.0.0-20241004153430-023329977675 h1:U94jQ2TQr1m3HNyE8efSdyaBbDrdPaWImXyenuKZ/nw=
|
||||
github.com/grafana/jsonparser v0.0.0-20241004153430-023329977675/go.mod h1:796sq+UcONnSlzA3RtlBZ+b/hrerkZXiEmO8oMjyRwY=
|
||||
github.com/grafana/loki/pkg/push v0.0.0-20250606045814-5db2927512ec h1:hRxt5+O/vCCkAMfPGbqEm3kehrvr8GqEQLad+/emQFg=
|
||||
github.com/grafana/loki/pkg/push v0.0.0-20250606045814-5db2927512ec/go.mod h1:jFooqvfqy5p+fhJIVTiNvw4+fhOLjpVhYJbEUZfMVA8=
|
||||
github.com/grafana/loki/v3 v3.5.1 h1:muG+UNmUuLBcWIRtankVPRyee58VSUVkZg0NMUPGP7M=
|
||||
github.com/grafana/loki/v3 v3.5.1/go.mod h1:BQiwDJF7fXVjFuKsN9U2uojfA2wE3sWOXbRFdN4y0mQ=
|
||||
github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8=
|
||||
github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls=
|
||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg=
|
||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU=
|
||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
|
||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
|
||||
github.com/hashicorp/consul/api v1.31.2 h1:NicObVJHcCmyOIl7Z9iHPvvFrocgTYo9cITSGg0/7pw=
|
||||
github.com/hashicorp/consul/api v1.31.2/go.mod h1:Z8YgY0eVPukT/17ejW+l+C7zJmKwgPHtjU1q16v/Y40=
|
||||
github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg=
|
||||
github.com/hashicorp/consul/sdk v0.16.1/go.mod h1:fSXvwxB2hmh1FMZCNl6PwX0Q/1wdWtHJcZ7Ea5tns0s=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY=
|
||||
github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI=
|
||||
github.com/hashicorp/go-msgpack/v2 v2.1.2 h1:4Ee8FTp834e+ewB71RDrQ0VKpyFdrKOjvYtnQ/ltVj0=
|
||||
github.com/hashicorp/go-msgpack/v2 v2.1.2/go.mod h1:upybraOAblm4S7rx0+jeNy+CWWhzywQsSRV5033mMu4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=
|
||||
github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
|
||||
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
|
||||
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/memberlist v0.5.3 h1:tQ1jOCypD0WvMemw/ZhhtH+PWpzcftQvgCorLu0hndk=
|
||||
github.com/hashicorp/memberlist v0.5.3/go.mod h1:h60o12SZn/ua/j0B6iKAZezA4eDaGsIuPO70eOaJ6WE=
|
||||
github.com/hashicorp/serf v0.10.2 h1:m5IORhuNSjaxeljg5DeQVDlQyVkhRIjJDimbkCa8aAc=
|
||||
github.com/hashicorp/serf v0.10.2/go.mod h1:T1CmSGfSeGfnfNy/w0odXQUR1rfECGd2Qdsp84DjOiY=
|
||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/jaegertracing/jaeger-idl v0.5.0 h1:zFXR5NL3Utu7MhPg8ZorxtCBjHrL3ReM1VoB65FOFGE=
|
||||
github.com/jaegertracing/jaeger-idl v0.5.0/go.mod h1:ON90zFo9eoyXrt9F/KN8YeF3zxcnujaisMweFY/rg5k=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
||||
github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ=
|
||||
github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE=
|
||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 h1:BpfhmLKZf+SjVanKKhCgf3bg+511DmU9eDQTen7LLbY=
|
||||
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
|
||||
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
|
||||
github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
|
||||
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
|
||||
github.com/opentracing-contrib/go-grpc v0.1.1 h1:Ws7IN1zyiL1DFqKQPhRXuKe5pLYzMfdxnC1qtajE2PE=
|
||||
github.com/opentracing-contrib/go-grpc v0.1.1/go.mod h1:Nu6sz+4zzgxXu8rvKfnwjBEmHsuhTigxRwV2RhELrS8=
|
||||
github.com/opentracing-contrib/go-stdlib v1.1.0 h1:cZBWc4pA4e65tqTJddbflK435S0tDImj6c9BMvkdUH0=
|
||||
github.com/opentracing-contrib/go-stdlib v1.1.0/go.mod h1:S0p+X9p6dcBkoMTL+Qq2VOvxKs9ys5PpYWXWqlCS0bQ=
|
||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
|
||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
|
||||
github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4=
|
||||
github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/exporter-toolkit v0.14.0 h1:NMlswfibpcZZ+H0sZBiTjrA3/aBFHkNZqE+iCj5EmRg=
|
||||
github.com/prometheus/exporter-toolkit v0.14.0/go.mod h1:Gu5LnVvt7Nr/oqTBUC23WILZepW0nffNo10XdhQcwWA=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/prometheus/prometheus v0.303.1 h1:He/2jRE6sB23Ew38AIoR1WRR3fCMgPlJA2E0obD2WSY=
|
||||
github.com/prometheus/prometheus v0.303.1/go.mod h1:WEq2ogBPZoLjj9x5K67VEk7ECR0nRD9XCjaOt1lsYck=
|
||||
github.com/prometheus/sigv4 v0.1.2 h1:R7570f8AoM5YnTUPFm3mjZH5q2k4D+I/phCWvZ4PXG8=
|
||||
github.com/prometheus/sigv4 v0.1.2/go.mod h1:GF9fwrvLgkQwDdQ5BXeV9XUSCH/IPNqzvAoaohfjqMU=
|
||||
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
|
||||
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo=
|
||||
github.com/redis/rueidis v1.0.19/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sercand/kuberesolver/v6 v6.0.0 h1:ScvS2Ga9snVkpOahln/BCLySr3/iBAHJf25u66DweZ0=
|
||||
github.com/sercand/kuberesolver/v6 v6.0.0/go.mod h1:Dxkqms3OJadP5zirIBPLi9FV8Qpys3T3w40XPEcVsu0=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sony/gobreaker/v2 v2.1.0 h1:av2BnjtRmVPWBvy5gSFPytm1J8BmN5AGhq875FfGKDM=
|
||||
github.com/sony/gobreaker/v2 v2.1.0/go.mod h1:dO3Q/nCzxZj6ICjH6J/gM0r4oAwBMVLY8YAQf+NTtUg=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
|
||||
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.32.0 h1:mac9BKRqwaX6zxHPDe3pvmWpwuuIM0vuXv2juCnQevE=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.32.0/go.mod h1:5eCOqeGphOyz6TsY3ZDNjE33SM/TFAK3RGuCL2naTgY=
|
||||
go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM=
|
||||
go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
|
||||
go.opentelemetry.io/otel/metric v0.30.0 h1:Hs8eQZ8aQgs0U49diZoaS6Uaxw3+bBE3lcMUKBFIk3c=
|
||||
go.opentelemetry.io/otel/metric v0.30.0/go.mod h1:/ShZ7+TS4dHzDFmfi1kSXMhMVubNoP0oIaBp70J6UXU=
|
||||
go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o=
|
||||
go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM=
|
||||
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8=
|
||||
github.com/tjhop/slog-gokit v0.1.4 h1:uj/vbDt3HaF0Py8bHPV4ti/s0utnO0miRbO277FLBKM=
|
||||
github.com/tjhop/slog-gokit v0.1.4/go.mod h1:Bbu5v2748qpAWH7k6gse/kw3076IJf6owJmh7yArmJs=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
go.etcd.io/etcd/api/v3 v3.5.4 h1:OHVyt3TopwtUQ2GKdd5wu3PmmipR4FTwCqoEjSyRdIc=
|
||||
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.4 h1:lrneYvz923dvC14R54XcA7FXoZ3mlGZAgmwhfm7HqOg=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v3 v3.5.4 h1:p83BUL3tAYS0OT/r0qglgc3M1JjhM0diV8DSWAhVXv4=
|
||||
go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/collector/pdata v1.28.1 h1:ORl5WLpQJvjzBVpHu12lqKMdcf/qDBwRXMcUubhybiQ=
|
||||
go.opentelemetry.io/collector/pdata v1.28.1/go.mod h1:asKE8MD/4SOKz1mCrGdAz4VO2U2HUNg8A6094uK7pq0=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.61.0 h1:RyrtJzu5MAmIcbRrwg75b+w3RlZCP0vJByDVzcpAe3M=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.61.0/go.mod h1:tirr4p9NXbzjlbruiRGp53IzlYrDk5CO2fdHj0sSSaY=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.61.0 h1:XfzKtKSrbtYk9TNCF8dkO0Y9M7IOfb4idCwBOTwGBiI=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.61.0/go.mod h1:N6otC+qXTD5bAnbK2O1f/1SXq3cX+3KYSWrkBUqG0cw=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 h1:0tY123n7CdWMem7MOVdKOt0YfshufLCwfE5Bob+hQuM=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0/go.mod h1:CosX/aS4eHnG9D7nESYpV753l4j9q5j3SL/PUYd2lR8=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.35.0 h1:UIrZgRBHUrYRlJ4V419lVb4rs2ar0wFzKNAebaP05XU=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.35.0/go.mod h1:0ciyFyYZxE6JqRAQvIgGRabKWDUmNdW3GAQb6y/RlFU=
|
||||
go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0 h1:bQ1Gvah4Sp8z7epSkgJaNTuZm7sutfA6Fji2/7cKFMc=
|
||||
go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0/go.mod h1:9b8Q9rH52NgYH3ShiTFB5wf18Vt3RTH/VMB7LDcC1ug=
|
||||
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
|
||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
||||
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2/go.mod h1:DvPtKE63knkDVP88qpatBj81JxN+w1bqfVbsbCbj1WY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 h1:tPLwQlXbJ8NSOfZc4OkgU5h2A38M4c9kfHSVc4PFQGs=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2/go.mod h1:QTnxBwT/1rBIgAG1goq6xMydfYOBKU6KTiYF4fp5zL8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0 h1:zwdo1gS2eH26Rg+CoqVQpEK1h8gvt5qyU5Kk5Bixvow=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0/go.mod h1:rUKCPscaRWWcqGT6HnEmYrK+YNe5+Sw64xgQTOJ5b30=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0 h1:gAU726w9J8fwr4qRDqu1GYMNNs4gXrU+Pv20/N1UpB4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0/go.mod h1:RboSDkp7N292rgu+T0MgVt2qgFGu6qa1RpZDOtpL76w=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.58.0 h1:CJAxWKFIqdBennqxJyOgnt5LqkeFRT+Mz3Yjz3hL+h8=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.58.0/go.mod h1:7qo/4CLI+zYSNbv0GMNquzuss2FVZo3OYrGh96n4HNc=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 h1:12vMqzLLNZtXuXbJhSENRg+Vvx+ynNilV8twBLBsXMY=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2/go.mod h1:ZccPZoPOoq8x3Trik/fCsba7DEYDUnN6yX79pgp2BUQ=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 h1:G8Xec/SgZQricwWBJF/mHZc7A02YHedfFDENwJEdRA0=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0/go.mod h1:PD57idA/AiFD5aqoxGxCvT/ILJPeHy3MjqU/NS7KogY=
|
||||
go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc=
|
||||
go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E=
|
||||
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
|
||||
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
||||
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
||||
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
|
||||
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
|
||||
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
|
||||
go.opentelemetry.io/otel/sdk/log v0.12.2 h1:yNoETvTByVKi7wHvYS6HMcZrN5hFLD7I++1xIZ/k6W0=
|
||||
go.opentelemetry.io/otel/sdk/log v0.12.2/go.mod h1:DcpdmUXHJgSqN/dh+XMWa7Vf89u9ap0/AAk/XGLnEzY=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc h1:uqxdywfHqqCl6LmZzI3pUnXT1RGFYyUgxj0AkWPFxi0=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc/go.mod h1:TY/N/FT7dmFrP/r5ym3g0yysP1DefqGpAZr4f82P0dE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
|
||||
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
|
||||
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
||||
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
||||
go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI=
|
||||
go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go4.org/netipx v0.0.0-20230125063823-8449b0a6169f h1:ketMxHg+vWm3yccyYiq+uK8D3fRmna2Fcj+awpQp84s=
|
||||
go4.org/netipx v0.0.0-20230125063823-8449b0a6169f/go.mod h1:tgPU4N2u9RByaTN3NC2p9xOzyFpte4jYwsIIRF7XlSc=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
|
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
|
||||
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
|
||||
google.golang.org/api v0.228.0 h1:X2DJ/uoWGnY5obVjewbp8icSL5U4FzuCfy9OjbLSnLs=
|
||||
google.golang.org/api v0.228.0/go.mod h1:wNvRS1Pbe8r4+IfBIniV8fwCpGwTrYa+kMUDiC5z5a4=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
|
||||
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
|
||||
k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA=
|
||||
k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94=
|
||||
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
|
||||
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
|
||||
25
integration/loki/line.go
Normal file
25
integration/loki/line.go
Normal file
@ -0,0 +1,25 @@
|
||||
package loki
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
|
||||
"github.com/foomo/sesamy-go/pkg/sesamy"
|
||||
)
|
||||
|
||||
type Line struct {
|
||||
Name sesamy.EventName `json:"name"`
|
||||
Params any `json:"params,omitempty"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
UserID string `json:"user_id,omitempty"`
|
||||
UserProperties map[string]any `json:"user_properties,omitempty"`
|
||||
Consent *mpv2.ConsentData `json:"consent,omitempty"`
|
||||
UserData *mpv2.UserData `json:"user_data,omitempty"`
|
||||
DebugMode bool `json:"debug_mode,omitempty"`
|
||||
SessionID string `json:"session_id,omitempty"`
|
||||
EngagementTimeMSec int64 `json:"engagement_time_msec,omitempty"`
|
||||
}
|
||||
|
||||
func (l *Line) Marshal() ([]byte, error) {
|
||||
return json.Marshal(l)
|
||||
}
|
||||
265
integration/loki/loki.go
Normal file
265
integration/loki/loki.go
Normal file
@ -0,0 +1,265 @@
|
||||
package loki
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
|
||||
"github.com/foomo/sesamy-go/pkg/utils"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/golang/snappy"
|
||||
"github.com/grafana/dskit/backoff"
|
||||
"github.com/grafana/loki/pkg/push"
|
||||
"github.com/grafana/loki/v3/pkg/logproto"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/common/model"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
pushEndpoint = "/loki/api/v1/push"
|
||||
defaultContentType = "application/x-protobuf"
|
||||
defaultMaxReponseBufferLen = 1024
|
||||
)
|
||||
|
||||
type (
|
||||
Loki struct {
|
||||
l *zap.Logger
|
||||
endpoint string
|
||||
|
||||
// channel for incoming logs
|
||||
entries chan logproto.Entry
|
||||
|
||||
// shutdown
|
||||
cancel context.CancelFunc
|
||||
|
||||
// options
|
||||
backoff *backoff.Config
|
||||
batchSize int
|
||||
bufferSize int
|
||||
userAgent string
|
||||
httpClient *http.Client
|
||||
}
|
||||
Option func(*Loki)
|
||||
)
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Options
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func WithHTTPClient(v *http.Client) Option {
|
||||
return func(l *Loki) {
|
||||
l.httpClient = v
|
||||
}
|
||||
}
|
||||
|
||||
func WithBackoffConfig(v *backoff.Config) Option {
|
||||
return func(l *Loki) {
|
||||
l.backoff = v
|
||||
}
|
||||
}
|
||||
|
||||
func WithUserAgent(v string) Option {
|
||||
return func(l *Loki) {
|
||||
l.userAgent = v
|
||||
}
|
||||
}
|
||||
|
||||
func WithBatchSize(v int) Option {
|
||||
return func(l *Loki) {
|
||||
l.batchSize = v
|
||||
}
|
||||
}
|
||||
|
||||
func WithBufferSize(v int) Option {
|
||||
return func(l *Loki) {
|
||||
l.bufferSize = v
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Constructor
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
// New creates an instance of `Push` which writes logs directly to given `lokiAddr`
|
||||
func New(l *zap.Logger, addr string, opts ...Option) *Loki {
|
||||
inst := &Loki{
|
||||
l: l,
|
||||
endpoint: addr + pushEndpoint,
|
||||
httpClient: http.DefaultClient,
|
||||
userAgent: "sesamy",
|
||||
batchSize: 10,
|
||||
bufferSize: 50,
|
||||
backoff: &backoff.Config{
|
||||
MinBackoff: 500 * time.Millisecond,
|
||||
MaxBackoff: 5 * time.Minute,
|
||||
MaxRetries: 10,
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
opt(inst)
|
||||
}
|
||||
}
|
||||
|
||||
inst.entries = make(chan logproto.Entry, inst.bufferSize) // Use a buffered channel so we can retry failed pushes without blocking WriteEntry
|
||||
|
||||
return inst
|
||||
}
|
||||
|
||||
// Start pulls lines out of the channel and sends them to Loki
|
||||
func (l *Loki) Start(ctx context.Context) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
l.cancel = cancel
|
||||
utils.Batch(ctx, l.entries, l.batchSize, l.process)
|
||||
}
|
||||
|
||||
func (l *Loki) Write(payload mpv2.Payload[any]) {
|
||||
for _, event := range payload.Events {
|
||||
// sanity check
|
||||
if event.Name == "" {
|
||||
l.l.Warn("received event without event name")
|
||||
continue
|
||||
}
|
||||
|
||||
line := Line{
|
||||
Name: event.Name,
|
||||
Params: event.Params,
|
||||
UserID: payload.UserID,
|
||||
Consent: payload.Consent,
|
||||
UserData: payload.UserData,
|
||||
ClientID: payload.ClientID,
|
||||
UserProperties: payload.UserProperties,
|
||||
DebugMode: payload.DebugMode,
|
||||
SessionID: payload.SessionID,
|
||||
EngagementTimeMSec: payload.EngagementTimeMSec,
|
||||
}
|
||||
|
||||
lineBytes, err := line.Marshal()
|
||||
if err != nil {
|
||||
l.l.Warn("failed to marshal line", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
if len(l.entries) == l.bufferSize {
|
||||
l.l.Warn("buffer size reached", zap.Int("size", l.bufferSize))
|
||||
}
|
||||
|
||||
var timestamp time.Time
|
||||
if payload.TimestampMicros > 0 {
|
||||
timestamp = time.UnixMicro(payload.TimestampMicros)
|
||||
} else {
|
||||
timestamp = time.Now()
|
||||
}
|
||||
|
||||
l.entries <- logproto.Entry{
|
||||
Line: string(lineBytes),
|
||||
Timestamp: timestamp,
|
||||
StructuredMetadata: push.LabelsAdapter{
|
||||
{
|
||||
Name: "event_name",
|
||||
Value: event.Name.String(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop will cancel any ongoing requests and stop the goroutine listening for requests
|
||||
func (l *Loki) Stop() {
|
||||
if l.cancel != nil {
|
||||
l.cancel()
|
||||
l.cancel = nil
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Private methods
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func (l *Loki) process(entries []logproto.Entry) {
|
||||
l.l.Debug("processing entries batch", zap.Int("num", len(entries)))
|
||||
|
||||
labels := model.LabelSet{
|
||||
"name": "events",
|
||||
"stream": "sesamy",
|
||||
}
|
||||
request, err := proto.Marshal(&logproto.PushRequest{
|
||||
Streams: []logproto.Stream{
|
||||
{
|
||||
Labels: labels.String(),
|
||||
Entries: entries,
|
||||
Hash: uint64(labels.Fingerprint()),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
l.l.Error("failed to marshal payload to json", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
payload := snappy.Encode(nil, request)
|
||||
|
||||
// We will use a timeout within each attempt to send
|
||||
back := backoff.New(context.Background(), *l.backoff)
|
||||
|
||||
// send log with retry
|
||||
for {
|
||||
var status int
|
||||
status, err = l.send(context.Background(), payload)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if status > 0 && status != 429 && status/100 != 5 {
|
||||
l.l.Error("failed to send entries, server rejected push with a non-retryable status code", zap.Error(err), zap.Int("status", status))
|
||||
break
|
||||
}
|
||||
|
||||
if !back.Ongoing() {
|
||||
l.l.Error("failed to send entries, retries exhausted, entries will be dropped", zap.Error(err), zap.Int("status", status))
|
||||
break
|
||||
}
|
||||
l.l.Warn("failed to send entries, retrying", zap.Error(err), zap.Int("status", status))
|
||||
back.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
// send makes one attempt to send the payload to Loki
|
||||
func (l *Loki) send(ctx context.Context, payload []byte) (int, error) {
|
||||
// Set a timeout for the request
|
||||
ctx, cancel := context.WithTimeout(ctx, l.httpClient.Timeout)
|
||||
defer cancel()
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, l.endpoint, bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return -1, errors.Wrap(err, "failed to create request")
|
||||
}
|
||||
req.Header.Set("Content-Type", defaultContentType)
|
||||
req.Header.Set("User-Agent", l.userAgent)
|
||||
|
||||
resp, err := l.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return -1, errors.Wrap(err, "failed to send payload")
|
||||
}
|
||||
status := resp.StatusCode
|
||||
if status/100 != 2 {
|
||||
scanner := bufio.NewScanner(io.LimitReader(resp.Body, defaultMaxReponseBufferLen))
|
||||
line := ""
|
||||
if scanner.Scan() {
|
||||
line = scanner.Text()
|
||||
}
|
||||
err = fmt.Errorf("server returned HTTP status %s (%d): %s", resp.Status, status, line)
|
||||
}
|
||||
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
l.l.Error("failed to close response body", zap.Error(err))
|
||||
}
|
||||
|
||||
return status, err
|
||||
}
|
||||
42
integration/loki/middleware.go
Normal file
42
integration/loki/middleware.go
Normal file
@ -0,0 +1,42 @@
|
||||
package loki
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/gtagencode"
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
|
||||
gtaghttp "github.com/foomo/sesamy-go/pkg/http/gtag"
|
||||
mpv2http "github.com/foomo/sesamy-go/pkg/http/mpv2"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func GTagMiddleware(loki *Loki) gtaghttp.Middleware {
|
||||
return func(next gtaghttp.MiddlewareHandler) gtaghttp.MiddlewareHandler {
|
||||
return func(l *zap.Logger, w http.ResponseWriter, r *http.Request, payload *gtag.Payload) error {
|
||||
err := next(l, w, r, payload)
|
||||
if err != nil {
|
||||
// encode to mpv2
|
||||
var mpv2Payload mpv2.Payload[any]
|
||||
if err := gtagencode.MPv2(*payload, &mpv2Payload); err != nil {
|
||||
return errors.Wrap(err, "failed to encode gtag to mpv2")
|
||||
}
|
||||
loki.Write(mpv2Payload)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func MPv2Middleware(loki *Loki) mpv2http.Middleware {
|
||||
return func(next mpv2http.MiddlewareHandler) mpv2http.MiddlewareHandler {
|
||||
return func(l *zap.Logger, w http.ResponseWriter, r *http.Request, payload *mpv2.Payload[any]) error {
|
||||
err := next(l, w, r, payload)
|
||||
if err != nil {
|
||||
loki.Write(*payload)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
29
integration/loki/service.go
Normal file
29
integration/loki/service.go
Normal file
@ -0,0 +1,29 @@
|
||||
package loki
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
loki *Loki
|
||||
}
|
||||
|
||||
func NewService(loki *Loki) *Service {
|
||||
return &Service{loki: loki}
|
||||
}
|
||||
|
||||
func (s *Service) Name() string {
|
||||
return "loki"
|
||||
}
|
||||
|
||||
// Start pulls lines out of the channel and sends them to Loki
|
||||
func (s *Service) Start(ctx context.Context) error {
|
||||
s.loki.Start(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close will cancel any ongoing requests and stop the goroutine listening for requests
|
||||
func (s *Service) Close(ctx context.Context) error {
|
||||
s.loki.Stop()
|
||||
return nil
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
package gtm
|
||||
|
||||
const (
|
||||
HeaderUUID = "Message-Uuid"
|
||||
ProviderName = "gtm"
|
||||
)
|
||||
@ -1,157 +0,0 @@
|
||||
package gtm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/ThreeDotsLabs/watermill/message"
|
||||
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrErrorResponse = errors.New("server responded with error status")
|
||||
ErrPublisherClosed = errors.New("publisher is closed")
|
||||
)
|
||||
|
||||
type (
|
||||
Publisher struct {
|
||||
url string
|
||||
client *http.Client
|
||||
marshalMessageFunc MarshalMessageFunc
|
||||
closed bool
|
||||
}
|
||||
PublisherOption func(*Publisher)
|
||||
)
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Constructor
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func NewPublisher(url string, opts ...PublisherOption) *Publisher {
|
||||
inst := &Publisher{
|
||||
url: url,
|
||||
client: http.DefaultClient,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(inst)
|
||||
}
|
||||
return inst
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Options
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func PublisherWithClient(v *http.Client) PublisherOption {
|
||||
return func(o *Publisher) {
|
||||
o.client = v
|
||||
}
|
||||
}
|
||||
|
||||
func PublisherWithMarshalMessageFunc(v MarshalMessageFunc) PublisherOption {
|
||||
return func(o *Publisher) {
|
||||
o.marshalMessageFunc = v
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Getter
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func (p *Publisher) Client() *http.Client {
|
||||
return p.client
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Public methods
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func (p *Publisher) Publish(topic string, messages ...*message.Message) error {
|
||||
if p.closed {
|
||||
return ErrPublisherClosed
|
||||
}
|
||||
|
||||
for _, msg := range messages {
|
||||
var event *mpv2.Event
|
||||
if err := json.Unmarshal(msg.Payload, &event); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
values, body, err := mpv2.Encode(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(msg.Context(), http.MethodPost, fmt.Sprintf("%s?%s", p.url, mpv2.EncodeValues(values)), body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create request")
|
||||
}
|
||||
|
||||
for s, s2 := range msg.Metadata {
|
||||
req.Header.Set(s, s2)
|
||||
}
|
||||
|
||||
// logFields := watermill.LogFields{
|
||||
// "uuid": msg.UUID,
|
||||
// "provider": ProviderName,
|
||||
// }
|
||||
|
||||
// p.l.Trace("Publishing message", logFields)
|
||||
|
||||
resp, err := p.client.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to publish message: %s", msg.UUID)
|
||||
}
|
||||
|
||||
if err = p.handleResponseBody(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
return errors.Wrap(ErrErrorResponse, resp.Status)
|
||||
}
|
||||
|
||||
// p.l.Trace("Message published", logFields)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Publisher) Close() error {
|
||||
if p.closed {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.closed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Private methods
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func (p *Publisher) handleResponseBody(resp *http.Response) error {
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < http.StatusBadRequest {
|
||||
return nil
|
||||
}
|
||||
|
||||
// body, err := io.ReadAll(resp.Body)
|
||||
// if err != nil {
|
||||
// return errors.Wrap(err, "could not read response body")
|
||||
// }
|
||||
|
||||
// logFields = logFields.Add(watermill.LogFields{
|
||||
// "http_status": resp.StatusCode,
|
||||
// "http_response": string(body),
|
||||
// })
|
||||
// p.l.Info("Server responded with error", logFields)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalMessageFunc transforms the message into a HTTP request to be sent to the specified url.
|
||||
type MarshalMessageFunc func(url string, msg *message.Message) (*http.Request, error)
|
||||
@ -1,5 +0,0 @@
|
||||
package keel
|
||||
|
||||
const (
|
||||
MetadataEventName = "X-Event-Name"
|
||||
)
|
||||
@ -1,217 +0,0 @@
|
||||
package keel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/ThreeDotsLabs/watermill"
|
||||
"github.com/ThreeDotsLabs/watermill/message"
|
||||
keellog "github.com/foomo/keel/net/http/log"
|
||||
keelhttputils "github.com/foomo/keel/utils/net/http"
|
||||
mp "github.com/foomo/sesamy-go/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMissingEventName = errors.New("missing event name")
|
||||
ErrContextCanceled = errors.New("request stopped without ACK received")
|
||||
ErrMessageNacked = errors.New("message nacked")
|
||||
ErrClosed = errors.New("subscriber already closed")
|
||||
)
|
||||
|
||||
type (
|
||||
Subscriber struct {
|
||||
l *zap.Logger
|
||||
uuidFunc func() string
|
||||
messages chan *message.Message
|
||||
middlewares []SubscriberMiddleware
|
||||
closed bool
|
||||
}
|
||||
SubscriberOption func(*Subscriber)
|
||||
SubscriberHandler func(l *zap.Logger, r *http.Request, event *mpv2.Event) error
|
||||
SubscriberMiddleware func(next SubscriberHandler) SubscriberHandler
|
||||
)
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Options
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func SubscriberWithUUIDFunc(v func() string) SubscriberOption {
|
||||
return func(o *Subscriber) {
|
||||
o.uuidFunc = v
|
||||
}
|
||||
}
|
||||
|
||||
func SubscriberWithMiddlewares(v ...SubscriberMiddleware) SubscriberOption {
|
||||
return func(o *Subscriber) {
|
||||
o.middlewares = append(o.middlewares, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func SubscriberWithLogger(fields ...zap.Field) SubscriberOption {
|
||||
return func(o *Subscriber) {
|
||||
o.middlewares = append(o.middlewares, func(next SubscriberHandler) SubscriberHandler {
|
||||
return func(l *zap.Logger, r *http.Request, event *mpv2.Event) error {
|
||||
fields := append(fields, zap.String("event_name", mp.GetDefault(event.EventName, "-").String()))
|
||||
if labeler, ok := keellog.LabelerFromRequest(r); ok {
|
||||
labeler.Add(fields...)
|
||||
}
|
||||
return next(l.With(fields...), r, event)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func SubscriberWithRequireEventName() SubscriberOption {
|
||||
return func(o *Subscriber) {
|
||||
o.middlewares = append(o.middlewares, func(next SubscriberHandler) SubscriberHandler {
|
||||
return func(l *zap.Logger, r *http.Request, event *mpv2.Event) error {
|
||||
if event.EventName == nil {
|
||||
return ErrMissingEventName
|
||||
}
|
||||
return next(l, r, event)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Constructor
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func NewSubscriber(l *zap.Logger, opts ...SubscriberOption) *Subscriber {
|
||||
inst := &Subscriber{
|
||||
l: l,
|
||||
uuidFunc: watermill.NewUUID,
|
||||
messages: make(chan *message.Message),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(inst)
|
||||
}
|
||||
return inst
|
||||
}
|
||||
|
||||
func (s *Subscriber) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
var values url.Values
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
values = r.URL.Query()
|
||||
case http.MethodPost:
|
||||
values = r.URL.Query()
|
||||
|
||||
// read request body
|
||||
out, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to read body: %s", err.Error()), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
// append request body to query
|
||||
if len(out) > 0 {
|
||||
v, err := url.ParseQuery(string(out))
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to parse extended url: %s", err.Error()), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
for s2, i := range v {
|
||||
values.Set(s2, i[0])
|
||||
}
|
||||
} else {
|
||||
values = r.URL.Query()
|
||||
}
|
||||
default:
|
||||
keelhttputils.ServerError(s.l, w, r, http.StatusMethodNotAllowed, errors.New(http.StatusText(http.StatusMethodNotAllowed)))
|
||||
return
|
||||
}
|
||||
|
||||
// unmarshal event
|
||||
var event *mpv2.Event
|
||||
if err := mpv2.Decode(values, &event); err != nil {
|
||||
keelhttputils.InternalServerError(s.l, w, r, errors.Wrap(err, "failed to marshal url values"))
|
||||
return
|
||||
}
|
||||
|
||||
// compose middlewares
|
||||
next := s.handle
|
||||
for _, middleware := range s.middlewares {
|
||||
next = middleware(next)
|
||||
}
|
||||
|
||||
// run handler
|
||||
if err := next(s.l, r, event); err != nil {
|
||||
keelhttputils.InternalServerError(s.l, w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Subscriber) handle(l *zap.Logger, r *http.Request, event *mpv2.Event) error {
|
||||
// marshal message payload
|
||||
payload, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal payload")
|
||||
}
|
||||
|
||||
msg := message.NewMessage(s.uuidFunc(), payload)
|
||||
l = l.With(zap.String("message_id", msg.UUID))
|
||||
if labeler, ok := keellog.LabelerFromRequest(r); ok {
|
||||
labeler.Add(zap.String("message_id", msg.UUID))
|
||||
}
|
||||
|
||||
if event.EventName != nil {
|
||||
msg.Metadata.Set(MetadataEventName, mp.Get(event.EventName).String())
|
||||
}
|
||||
|
||||
// TODO filter headers?
|
||||
for name, headers := range r.Header {
|
||||
msg.Metadata.Set(name, strings.Join(headers, ","))
|
||||
}
|
||||
|
||||
for k, v := range msg.Metadata {
|
||||
l = l.With(zap.String(k, v))
|
||||
}
|
||||
|
||||
// TODO different context?
|
||||
ctx, cancelCtx := context.WithCancel(r.Context())
|
||||
msg.SetContext(ctx)
|
||||
defer cancelCtx()
|
||||
|
||||
// send message
|
||||
s.messages <- msg
|
||||
|
||||
// wait for ACK
|
||||
select {
|
||||
case <-msg.Acked():
|
||||
l.Debug("message acked")
|
||||
return nil
|
||||
case <-msg.Nacked():
|
||||
l.Debug("message nacked")
|
||||
return ErrMessageNacked
|
||||
case <-r.Context().Done():
|
||||
l.Debug("message cancled")
|
||||
return ErrContextCanceled
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Subscriber) Subscribe(ctx context.Context, topic string) (<-chan *message.Message, error) {
|
||||
return s.messages, nil
|
||||
}
|
||||
|
||||
// Close closes all subscriptions with their output channels and flush offsets etc. when needed.
|
||||
func (s *Subscriber) Close() error {
|
||||
if s.closed {
|
||||
return ErrClosed
|
||||
}
|
||||
s.closed = true
|
||||
|
||||
close(s.messages)
|
||||
return nil
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ThreeDotsLabs/watermill/message"
|
||||
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func EventHandler(eventHandler func(event *mpv2.Event, msg *message.Message) error) func(msg *message.Message) ([]*message.Message, error) {
|
||||
return func(msg *message.Message) ([]*message.Message, error) {
|
||||
var event *mpv2.Event
|
||||
if err := json.Unmarshal(msg.Payload, &event); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal event")
|
||||
}
|
||||
|
||||
if err := eventHandler(event, msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal event")
|
||||
}
|
||||
msg.Payload = b
|
||||
|
||||
return []*message.Message{msg}, nil
|
||||
}
|
||||
}
|
||||
@ -1,104 +0,0 @@
|
||||
package measurementprotocol
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func Set[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
|
||||
func SetInt(v int) *string {
|
||||
if v == 0 {
|
||||
return nil
|
||||
}
|
||||
return Set(fmt.Sprintf("%d", v))
|
||||
}
|
||||
|
||||
func SetUInt(v uint) *string {
|
||||
if v == 0 {
|
||||
return nil
|
||||
}
|
||||
return Set(fmt.Sprintf("%d", v))
|
||||
}
|
||||
|
||||
func SetInt32(v int32) *string {
|
||||
if v == 0 {
|
||||
return nil
|
||||
}
|
||||
return Set(fmt.Sprintf("%d", v))
|
||||
}
|
||||
|
||||
func SetUInt32(v uint32) *string {
|
||||
if v == 0 {
|
||||
return nil
|
||||
}
|
||||
return Set(fmt.Sprintf("%d", v))
|
||||
}
|
||||
|
||||
func SetInt64(v int64) *string {
|
||||
if v == 0 {
|
||||
return nil
|
||||
}
|
||||
return Set(fmt.Sprintf("%d", v))
|
||||
}
|
||||
|
||||
func SetUInt64(v uint64) *string {
|
||||
if v == 0 {
|
||||
return nil
|
||||
}
|
||||
return Set(fmt.Sprintf("%d", v))
|
||||
}
|
||||
|
||||
func SetFloat32(v float32) *string {
|
||||
if v == 0 {
|
||||
return nil
|
||||
}
|
||||
return Set(fmt.Sprintf("%f", v))
|
||||
}
|
||||
|
||||
func SetFloat64(v float64) *string {
|
||||
if v == 0 {
|
||||
return nil
|
||||
}
|
||||
return Set(fmt.Sprintf("%f", v))
|
||||
}
|
||||
|
||||
func SetString(v string) *string {
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
return Set(v)
|
||||
}
|
||||
|
||||
func SetBool(v bool) *string {
|
||||
if !v {
|
||||
return nil
|
||||
}
|
||||
return Set("1")
|
||||
}
|
||||
|
||||
func SetStringMap(v map[string]string) map[string]string {
|
||||
if len(v) == 0 {
|
||||
return nil
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func AddStringMap(t map[string]string, k string, v *string) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
t[k] = *v
|
||||
}
|
||||
|
||||
func Get[T any](v *T) T {
|
||||
return *v
|
||||
}
|
||||
|
||||
func GetDefault[T any](v *T, fallback T) T {
|
||||
if v == nil {
|
||||
return fallback
|
||||
}
|
||||
return *v
|
||||
}
|
||||
@ -1,98 +0,0 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func Decode(input url.Values, output interface{}) error {
|
||||
data := Data{}
|
||||
|
||||
// decode values
|
||||
for k, v := range input {
|
||||
// handle maps
|
||||
if ok, err := DecodeMapValue(k, v, data); err != nil {
|
||||
return err
|
||||
} else if ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// handle slices
|
||||
if ok, err := DecodeRegexValue(k, v, RegexProduct, data, ParameterItem); err != nil {
|
||||
return err
|
||||
} else if ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// default
|
||||
v, err := url.QueryUnescape(v[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data[k] = v
|
||||
}
|
||||
|
||||
if err := mapstructure.WeakDecode(data, output); err != nil {
|
||||
return errors.Wrap(err, "failed to weakly decode query")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func DecodeMapValue(k string, v []string, data Data) (bool, error) {
|
||||
if strings.Contains(k, ".") {
|
||||
parts := strings.Split(k, ".")
|
||||
if _, ok := data[parts[0]]; !ok {
|
||||
data[parts[0]] = map[string]any{}
|
||||
}
|
||||
if value, ok := data[parts[0]].(map[string]any); ok {
|
||||
v, err := url.QueryUnescape(v[0])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
value[strings.Join(parts[1:], ".")] = v
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// DecodeRegexValue e.g. `pr1=idSKU_123456` = map["pr"][]map["id"]="SKU_123456"
|
||||
func DecodeRegexValue(k string, v []string, r *regexp.Regexp, data Data, key string) (bool, error) {
|
||||
if r.MatchString(k) {
|
||||
value, err := DecodeObjectValue(v[0])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if value != nil {
|
||||
v, ok := data[key].([]map[string]any)
|
||||
if !ok {
|
||||
v = []map[string]any{}
|
||||
}
|
||||
v = append(v, value)
|
||||
data[key] = v
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// DecodeObjectValue e.g. `idSKU_123456` = map["id"]="SKU_123456"
|
||||
func DecodeObjectValue(s string) (map[string]any, error) {
|
||||
if len(s) == 0 {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
ret := map[string]any{}
|
||||
for _, part := range strings.Split(s, "~") {
|
||||
v, err := url.QueryUnescape(part[2:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret[part[0:2]] = v
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
@ -1,134 +0,0 @@
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
want error
|
||||
}{
|
||||
{
|
||||
name: "page_view",
|
||||
args: "v=2&tid=G-123456>m=45je42s0v893689383z8894072534za200&_p=1709286636310&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=292234677.1707898933&ul=en-us&sr=3840x1600&uaa=arm&uab=64&uafvl=Chromium&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pae=1&pscdl=noapi&_s=1&sid=1709286452&sct=7&seg=1&dl=https%3A%2F%2Fwww.homemade.ch%2Fprodukt%2Fkuhn-rikon-wiegemesser-900047100%3Fid%3D900047100&dr=https%3A%2F%2Fwww.homemade.ch%2Fmesser-besteck&dt=Wiegemesser&en=page_view&tfd=5682",
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "add_to_cart",
|
||||
args: "v=2&tid=G-123456>m=45je42s0v9175354889z89175348963za200&_p=1709297934217&_dbg=1&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=1220643501.1708014725&ul=en-us&sr=3840x1600&_fplc=0&ur=DE-BY&uaa=arm&uab=64&uafvl=Chromium&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pscdl=noapi&_eu=IA&sst.uc=DE&sst.etld=google.de&sst.gcsub=region1&sst.gcd=13l3l3l3l1&sst.tft=1709297934217&_s=8&cu=USD&sid=1709296380&sct=7&seg=1&dl=https%3A%2F%2Fsniffer.cloud.bestbytes.net%2F%3Fgtm_debug%3D1709297933868&dr=https%3A%2F%2Ftagassistant.google.com%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=add_to_cart&pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Store~cpSUMMER_FUN~ds2.22~lp5~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr10.01~qt3&epn.value=30.03&tfd=15129&richsstsse",
|
||||
want: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
u, err := url.ParseQuery(tt.args)
|
||||
require.NoError(t, err)
|
||||
e := &mpv2.Event{}
|
||||
if err := mpv2.Decode(u, e); !assert.ErrorIs(t, err, tt.want) {
|
||||
t.Errorf("Decode() = %v, want %v", err, tt.want)
|
||||
t.Log(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeMapValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "v=2",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "ep.foo=bar",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "ep.foo.bar=bar",
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
d := mpv2.Data{}
|
||||
values, err := url.ParseQuery(tt.name)
|
||||
require.NoError(t, err)
|
||||
for k, v := range values {
|
||||
if got, err := mpv2.DecodeMapValue(k, v, d); assert.NoError(t, err) && got != tt.want {
|
||||
t.Errorf("DecodeMapValue() = %v, want %v", got, tt.want)
|
||||
t.Log(d)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeProductValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "v=2",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Merchandise%20Store~cpSUMMER_FUN~ds2.22~lp0~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20Products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr9.99~qt1",
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
d := mpv2.Data{}
|
||||
values, err := url.ParseQuery(tt.name)
|
||||
require.NoError(t, err)
|
||||
for k, v := range values {
|
||||
if got, err := mpv2.DecodeRegexValue(k, v, mpv2.RegexProduct, d, "pr"); assert.NoError(t, err) && got != tt.want {
|
||||
t.Errorf("decodeMapValue() = %v, want %v", got, tt.want)
|
||||
t.Log(d)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeObjectValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
want map[string]any
|
||||
}{
|
||||
{
|
||||
name: "",
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "idSKU_12345~nmStan%20and%20Friends%20Tee~qt1",
|
||||
want: map[string]any{
|
||||
"id": "SKU_12345",
|
||||
"nm": "Stan and Friends Tee",
|
||||
"qt": "1",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got, err := mpv2.DecodeObjectValue(tt.name); assert.NoError(t, err) && !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("decodeObjectValue() = %v, want %v", got, tt.want)
|
||||
t.Log(got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEncode(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
want error
|
||||
}{
|
||||
{
|
||||
name: "page_view",
|
||||
args: "v=2&tid=G-123456>m=45je42s0v893689383z8894072534za200&_p=1709286636310&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=292234677.1707898933&ul=en-us&sr=3840x1600&uaa=arm&uab=64&uafvl=Chromium&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pae=1&pscdl=noapi&_s=1&sid=1709286452&sct=7&seg=1&dl=https%3A%2F%2Fwww.homemade.ch%2Fprodukt%2Fkuhn-rikon-wiegemesser-900047100%3Fid%3D900047100&dr=https%3A%2F%2Fwww.homemade.ch%2Fmesser-besteck&dt=Wiegemesser&en=page_view&tfd=5682",
|
||||
want: nil,
|
||||
},
|
||||
// {
|
||||
// name: "add_to_cart",
|
||||
// args: "v=2&tid=G-123456>m=45je42s0v9175354889z89175348963za200&_p=1709297934217&_dbg=1&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=1220643501.1708014725&ul=en-us&sr=3840x1600&_fplc=0&ur=DE-BY&uaa=arm&uab=64&uafvl=Chromium&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pscdl=noapi&_eu=IA&sst.uc=DE&sst.etld=google.de&sst.gcsub=region1&sst.gcd=13l3l3l3l1&sst.tft=1709297934217&_s=8&cu=USD&sid=1709296380&sct=7&seg=1&dl=https%3A%2F%2Fsniffer.cloud.bestbytes.net%2F%3Fgtm_debug%3D1709297933868&dr=https%3A%2F%2Ftagassistant.google.com%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=add_to_cart&pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Store~cpSUMMER_FUN~ds2.22~lp5~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr10.01~qt3&epn.value=30.03&tfd=15129&richsstsse",
|
||||
// want: nil,
|
||||
// },
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
u, err := url.ParseQuery(tt.args)
|
||||
require.NoError(t, err)
|
||||
e := &mpv2.Event{}
|
||||
require.NoError(t, mpv2.Decode(u, e))
|
||||
if !assert.Empty(t, e.Unknown) {
|
||||
t.Errorf("Encode() = %v, want %v", e.Unknown, nil)
|
||||
}
|
||||
if out, _, err := mpv2.Encode(e); assert.NoError(t, err) {
|
||||
assert.EqualValues(t, u, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,263 +0,0 @@
|
||||
package v2
|
||||
|
||||
// See https://www.thyngster.com/ga4-measurement-protocol-cheatsheet/
|
||||
type Event struct {
|
||||
// --- Request parameters ---
|
||||
|
||||
// Defines que current protocol version being used.
|
||||
// Example: 2
|
||||
ProtocolVersion *string `json:"v,omitempty" mapstructure:"v,omitempty"`
|
||||
// Current Stream ID / Measurement ID
|
||||
// Example: G-THYNGSTER
|
||||
TrackingID *string `json:"tid,omitempty" mapstructure:"tid,omitempty"`
|
||||
// If the current hit is coming was generated from GTM, it will contain a hash of current GTM/GTAG config
|
||||
// Example: 2oear0
|
||||
GTMHashInfo *string `json:"gtm,omitempty" mapstructure:"gtm,omitempty"`
|
||||
// Is a random hash generated on the page load.
|
||||
// Examole: 456193680
|
||||
RandomPageLoadHash *string `json:"_p,omitempty" mapstructure:"_p,omitempty"`
|
||||
// Browser screen resolution in format width x height
|
||||
// Example: 2560x1440
|
||||
ScreenResolution *string `json:"sr,omitempty" mapstructure:"sr,omitempty"`
|
||||
// Browser active locale.
|
||||
// Example: es-es
|
||||
UserLanguage *string `json:"ul,omitempty" mapstructure:"ul,omitempty"`
|
||||
// Current Document Hostname
|
||||
// Exampple: www.analytics-debugger.com
|
||||
DocumentHostname *string `json:"dh,omitempty" mapstructure:"dh,omitempty"`
|
||||
// Google Analytics Client Id
|
||||
// Example: 281344611.1635634925
|
||||
ClientID *string `json:"cid,omitempty" mapstructure:"cid,omitempty"`
|
||||
// Current hits counter for the current page load
|
||||
// Example: 1
|
||||
HitCounter *string `json:"_s,omitempty" mapstructure:"_s,omitempty"`
|
||||
// This is supposed to be to enrich the GA4 hits to send data to SGTM, at this point is always set as an empty value...
|
||||
Richsstsse *string `json:"richsstsse,omitempty" mapstructure:"richsstsse,omitempty"`
|
||||
|
||||
// --- Client Hints ---
|
||||
|
||||
// Example: x86
|
||||
UserAgentArchitecture *string `json:"uaa,omitempty" mapstructure:"uaa,omitempty"`
|
||||
// The "bitness" of the user-agent's underlying CPU architecture. This is the size in bits of an integer or memory address—typically 64 or 32 bits.
|
||||
// Example: 32 | 64
|
||||
UserAgentBitness *string `json:"uab,omitempty" mapstructure:"uab,omitempty"`
|
||||
// The brand and full version information for each brand associated with the browser, in a comma-separated list
|
||||
// Example: Google Chrome;105.0.5195.127|Not)A;Brand;8.0.0.0|Chromium;105.0.5195.127
|
||||
UserAgentFullVersionList *string `json:"uafvl,omitempty" mapstructure:"uafvl,omitempty"`
|
||||
// Indicates whether the browser is on a mobile device
|
||||
// Example: 1
|
||||
UserAgentMobile *string `json:"uamb,omitempty" mapstructure:"uamb,omitempty"`
|
||||
// The device model on which the browser is running. Will likely be empty for desktop browsers
|
||||
// Example: Nexus 6
|
||||
UserAgentModel *string `json:"uam,omitempty" mapstructure:"uam,omitempty"`
|
||||
// The platform or operating system on which the user agent is running
|
||||
// Example: Chromium OS | macOS | Android | iOS
|
||||
UserAgentPlatform *string `json:"uap,omitempty" mapstructure:"uap,omitempty"`
|
||||
// The version of the operating system on which the user agent is running
|
||||
// Example: 14.0.0
|
||||
UserAgentPlatformVersion *string `json:"uapv,omitempty" mapstructure:"uapv,omitempty"`
|
||||
// Whatever Windows On Windows 64 Bit is supported. Used by "WoW64-ness" sites. ( running 32bits app on 64bits windows)
|
||||
// Example: 1
|
||||
UserAgentWOW64 *string `json:"uaw,omitempty" mapstructure:"uaw,omitempty"`
|
||||
|
||||
// --- Shared ---
|
||||
|
||||
// Actual page's Pathname. It does not include the hostname, quertyString or Fragment
|
||||
// Example: /hire-me
|
||||
DocumentLocation *string `json:"dl,omitempty" mapstructure:"dl,omitempty"`
|
||||
// Actual page's Title
|
||||
// Example: Hire Me
|
||||
DocumentTitle *string `json:"dt,omitempty" mapstructure:"dt,omitempty"`
|
||||
// Actual page's Referrer
|
||||
// Example:
|
||||
DocumentReferrer *string `json:"dr,omitempty" mapstructure:"dr,omitempty"`
|
||||
// Unknown. Value ccd.{{HASH}}. The hash in based on various internal parameters. Some kind of usage hash.
|
||||
// Example: ccd.AAB
|
||||
Z *string `json:"_z,omitempty" mapstructure:"_z,omitempty"`
|
||||
// This is added when an event is generated from rules (from the admin). Actually is hash of the "GA4_EVENT" string
|
||||
// Example: Q
|
||||
EventUsage *string `json:"_eu,omitempty" mapstructure:"_eu,omitempty"`
|
||||
// Unknown
|
||||
// Example:
|
||||
EventDebugID *string `json:"edid,omitempty" mapstructure:"edid,omitempty"`
|
||||
// If an event contains this parameters it won't be processed and it will show on on the debug View in GA4
|
||||
// Example: 1
|
||||
IsDebug *string `json:"_dbg,omitempty" mapstructure:"_dbg,omitempty"`
|
||||
// If the current request has a referrer, it will be ignored at processing level
|
||||
// Example: 1
|
||||
IgnoreReferrer *string `json:"ir,omitempty" mapstructure:"ir,omitempty"`
|
||||
// Traffic Type
|
||||
// Example: 1
|
||||
TrafficType *string `json:"tt,omitempty" mapstructure:"tt,omitempty"`
|
||||
// Current Google Consent Status. Format 'G1'+'AdsStorageBoolStatus'`+'AnalyticsStorageBoolStatus'
|
||||
// Example: G101
|
||||
GoogleConsentStatus *string `json:"gcs,omitempty" mapstructure:"gcs,omitempty"`
|
||||
// Will be added with the value "1" if the Google Consent has just been updated (wait_for_update setting on GTAG)
|
||||
// Example: 1
|
||||
GoogleConsentUpdate *string `json:"gcu,omitempty" mapstructure:"gcu,omitempty"`
|
||||
// Documented values, 1 or 2, no more info on the meaning
|
||||
// Example: 2
|
||||
GoogleConsentUpdateType *string `json:"gcut,omitempty" mapstructure:"gcut,omitempty"`
|
||||
// Will be added with the value "1" if the Google Consent had a default value before getting an update
|
||||
// Example: G111
|
||||
GoogleConsentDefault *string `json:"gcd,omitempty" mapstructure:"gcd,omitempty"`
|
||||
// Will be set to 1 is the current page has a linker and this last one is valid
|
||||
// Example: 1
|
||||
IsGoogleLinkerValid *string `json:"_glv,omitempty" mapstructure:"_glv,omitempty"`
|
||||
|
||||
// --- Campaign Attributes ---
|
||||
|
||||
// Campaign Medium ( utm_medium ), this will override the current values read from the url
|
||||
// Example: cpc
|
||||
CampaignMedium *string `json:"cm,omitempty" mapstructure:"cm,omitempty"`
|
||||
// Campaign Source ( utm_source ), this will override the current values read from the url
|
||||
// Example: google
|
||||
CampaignSource *string `json:"cs,omitempty" mapstructure:"cs,omitempty"`
|
||||
// Campaign Name ( utm_campaign ), this will override the current values read from the url
|
||||
// Example: cpc
|
||||
CampaignName *string `json:"cn,omitempty" mapstructure:"cn,omitempty"`
|
||||
// Campaign Content ( utm_content ), this will override the current values read from the url
|
||||
// Example: big banner
|
||||
CampaignContent *string `json:"cc,omitempty" mapstructure:"cc,omitempty"`
|
||||
// Campaign Term ( utm_term ), this will override the current values read from the url
|
||||
// Example: summer
|
||||
CampaignTerm *string `json:"ck,omitempty" mapstructure:"ck,omitempty"`
|
||||
// Campaign Creative Format ( utm_creative_format ), this will override the current values read from the url
|
||||
// Example: native
|
||||
CampaignCreativeFormat *string `json:"ccf,omitempty" mapstructure:"ccf,omitempty"`
|
||||
// Campaign Marketing Tactic ( utm_marketing_tactic ), this will override the current values read from the url
|
||||
// Example: prospecting
|
||||
CampaignMarketingTactic *string `json:"cmt,omitempty" mapstructure:"cmt,omitempty"`
|
||||
// Random Number used to Dedupe gclid
|
||||
// Example: 342342343
|
||||
GclidDeduper *string `json:"_rnd,omitempty" mapstructure:"_rnd,omitempty"`
|
||||
|
||||
// --- Event Parameters ---
|
||||
|
||||
// Current Event Name.
|
||||
// Example: page_view
|
||||
EventName *EventName `json:"en,omitempty" mapstructure:"en,omitempty"`
|
||||
// It's the total engagement time in milliseconds since the last event. The engagement time is measured only when the current page is visible and active ( ie: the browser window/tab must be active and visible ), for this GA4 uses the window.events: focus, blur, pageshow, pagehide and the document:visibilitychange, these will determine when the timer starts and pauses
|
||||
// Example: 1234
|
||||
EngagementTime *string `json:"_et,omitempty" mapstructure:"_et,omitempty"`
|
||||
// Defines a parameter for the current Event
|
||||
// Example: ep.page_type: checkout
|
||||
EventParameter map[string]string `json:"ep,omitempty" mapstructure:"ep,omitempty"`
|
||||
// Defines a parameter for the current Event
|
||||
// Example: epn.plays_count: 42
|
||||
EventParameterNumber map[string]string `json:"epn,omitempty" mapstructure:"epn,omitempty"`
|
||||
// If the current event is set as a conversion on the admin interacted the evfent will have this value present
|
||||
// Example: 1
|
||||
IsConversion *string `json:"_c,omitempty" mapstructure:"_c,omitempty"`
|
||||
// External Event
|
||||
ExternalEvent *string `json:"_ee,omitempty" mapstructure:"_ee,omitempty"`
|
||||
|
||||
// --- Session / User Related ---
|
||||
|
||||
// Current User ID
|
||||
// Example: 1635691016
|
||||
UserID *string `json:"uid,omitempty" mapstructure:"uid,omitempty"`
|
||||
// Current Firebase ID
|
||||
// Example: HASHSAH
|
||||
FirebaseID *string `json:"_fid,omitempty" mapstructure:"_fid,omitempty"`
|
||||
// GA4 Session Id. This comes from the GA4 Cookie. It may be different for each Stream ID Configured on the site
|
||||
// Example: 1635691016
|
||||
SessionID *string `json:"sid,omitempty" mapstructure:"sid,omitempty"`
|
||||
// Count of sessions recorded by GA4. This value increases by one each time a new session is detected ( when the session expires )
|
||||
// Example: 10
|
||||
SessionCount *string `json:"sct,omitempty" mapstructure:"sct,omitempty"`
|
||||
// If the current user is engaged in any way, this value will be 1
|
||||
// Example:
|
||||
SessionEngagment *string `json:"seg,omitempty" mapstructure:"seg,omitempty"`
|
||||
// Defines an user Propery for the current Measurement ID
|
||||
// Example: up.is_premium_user: yes
|
||||
UserProperty map[string]string `json:"up,omitempty" mapstructure:"up,omitempty"`
|
||||
// Defines an user Propery for the current Measurement ID
|
||||
// Example:
|
||||
UserPropertyNumber map[string]string `json:"upn,omitempty" mapstructure:"upn,omitempty"`
|
||||
// If the "_ga_THYNGSTER" cookie is not set, the first event will have this value present. This will internally create a new "first_visit" event on GA4. If this event is also a conversion the value will be "2" if not, will be "1"
|
||||
// Example: 1|2
|
||||
FirstVisit *string `json:"_fv,omitempty" mapstructure:"_fv,omitempty"`
|
||||
// If the "_ga_THYNGSTER" cookie last session time value is older than 1800 seconds, the current event will have this value present. This will internally create a new "session_start" event on GA4. If this event is also a conversion the value will be "2" if not, will be "1"
|
||||
// Example: 1|2
|
||||
SessionStart *string `json:"_ss,omitempty" mapstructure:"_ss,omitempty"`
|
||||
// This seems to be related to the ServerSide hits, it's 0 if the FPLC Cookie is not present and to the current value if it's coming from a Cross Domain linker
|
||||
// Example: bVhVicbfiSXaGNxeawKaPlDQc9QXPD6bKcsn36Elden6wZNb7Q5X1iXlkTVP5iP3H3y76cgM3UIgHCaRsYfPoyLGlbiIYMPRjvnUU7KWbdWLagodzxjrlPnvaRZJkw
|
||||
FirstPartyLinkerCookie *string `json:"_fplc,omitempty" mapstructure:"_fplc,omitempty"`
|
||||
// If the current user has a GA4 session cookie, but not a GA (_ga) client id cookie, this parameter will be added to the hit
|
||||
// Example: 1
|
||||
NewSessionID *string `json:"_nsi,omitempty" mapstructure:"_nsi,omitempty"`
|
||||
// You may find this parameter if using some vendor plugin o platform ( ie: using shopify integration or a prestashop plugin )
|
||||
// Example: jdhsd87
|
||||
GoogleDeveloperID *string `json:"gdid,omitempty" mapstructure:"gdid,omitempty"`
|
||||
// Added to report the current country for the user under some circumstanced. To be documented.
|
||||
// Example: ES
|
||||
UserCountry *string `json:"_uc,omitempty" mapstructure:"_uc,omitempty"`
|
||||
// Example: DE-BY
|
||||
UserRegion *string `json:"ur,omitempty" mapstructure:"ur,omitempty"`
|
||||
|
||||
// --- eCommerce ---
|
||||
|
||||
// Currency Code. ISO 4217
|
||||
// Example: JPY
|
||||
Currency *string `json:"cu,omitempty" mapstructure:"cu,omitempty"`
|
||||
// Example:
|
||||
Items []*Item `json:"pr,omitempty" mapstructure:"pr,omitempty"`
|
||||
// Promotion Impression/Click Tracking. Promotion Id
|
||||
// Example: summer-offer
|
||||
PromotionID *string `json:"pi,omitempty" mapstructure:"pi,omitempty"`
|
||||
// Promotion Impression/Click Tracking. Promotion Name
|
||||
// Example: summer-offer
|
||||
PromotionName *string `json:"pn,omitempty" mapstructure:"pn,omitempty"`
|
||||
// Promotion Impression/Click Tracking. Creative Name
|
||||
// Example: red-car
|
||||
// CreativeName *string `json:"cn,omitempty" mapstructure:"cn,omitempty"`
|
||||
// Promotion Impression/Click Tracking. Promotion Slot / Position
|
||||
// Example: slide-3
|
||||
// CreativeSlot *string `json:"cs,omitempty" mapstructure:"cs,omitempty"`
|
||||
// Google Place ID: Refer to: https://developers.google.com/maps/documentation/places/web-service/place-id . Seems to be inherited from Firebase, not sure about the current use on GA4
|
||||
// Example: ChIJiyj437sx3YAR9kUWC8QkLzQ
|
||||
LocationID *string `json:"lo,omitempty" mapstructure:"lo,omitempty"`
|
||||
|
||||
// --- Uncategorized / Missing Info ---
|
||||
// Example: 1
|
||||
GTMUp *string `json:"gtm_up,omitempty" mapstructure:"gtm_up,omitempty"`
|
||||
// Documented values, 1,2,3: Not sure when it's added.
|
||||
EuropeanConsentModeEnabledID *string `json:"_ecid,omitempty" mapstructure:"_ecid,omitempty"`
|
||||
// Example:
|
||||
UEI *string `json:"_uei,omitempty" mapstructure:"_uei,omitempty"`
|
||||
// It's set when a Google Join is created/imported. Google Signals
|
||||
// Example: 1
|
||||
CreateGoogleJoin *string `json:"_gaz,omitempty" mapstructure:"_gaz,omitempty"`
|
||||
// Example: Redact Device Info. Need Investigation about functionality
|
||||
RedactDeviceInfo *string `json:"_rdi,omitempty" mapstructure:"_rdi,omitempty"`
|
||||
// Geo Granularity. Need Investigation about functionality
|
||||
GeoGranularity *string `json:"_geo,omitempty" mapstructure:"_geo,omitempty"`
|
||||
// Sent on sites that implement the US Privacy User Signal Mechanism, sent if window.__uspapi is present and returning a value.
|
||||
// Example: 1YNY
|
||||
USPrivacySignal *string `json:"us_privacy,omitempty" mapstructure:"us_privacy,omitempty"`
|
||||
// Sent on sites that implements IAB GDPR-Transparency-and-Consent-Framework( TCFv2 ) Mechanism. sent if window.__tcfapi is present and returning a valid value.
|
||||
// Example: 1
|
||||
GDPR *string `json:"gdpr,omitempty" mapstructure:"gdpr,omitempty"`
|
||||
// Sent on sites that implements IAB GDPR-Transparency-and-Consent-Framework( TCFv2 ) Mechanism. sent if window.__tcfapi is present and returning a valid value.
|
||||
// Example: CPfPdAAPfPdAAAHABBENCgCsAP_AAAAAAAAAI_tf_X__b3_j-_5___t0eY1f9_7__-0zjhfdl-8N3f_X_L8X_2M7vF36tq4KuR4Eu3LBIQdlHOHcTUmw6okVrzPsbk2cr7NKJ7PEmnMbeydYGH9_n1_z-ZKY7_____77__-____3_____-_f___5_3____f_V__97fn9_____9_P___9v__9__________3___gAAAJJQAYAAgj-GgAwABBH8VABgACCP5SADAAEEfx0AGAAII_kIAMAAQR_CQAYAAgj-IgAwABBH8ZABgACCP4A.f_gAAAAAAAAA
|
||||
GDPRConsent *string `json:"gdpr_consent,omitempty" mapstructure:"gdpr_consent,omitempty"`
|
||||
// Example: sypham
|
||||
NonPersonalizedAds *string `json:"npa,omitempty" mapstructure:"npa,omitempty"`
|
||||
// Example: 1
|
||||
ARE *string `json:"are,omitempty" mapstructure:"are,omitempty"`
|
||||
// Example: 1
|
||||
DigitalMarketAct *string `json:"dma,omitempty" mapstructure:"dma,omitempty"`
|
||||
// Example: sypham
|
||||
DigitalMarketActParameters *string `json:"dma_cps,omitempty" mapstructure:"dma_cps,omitempty"`
|
||||
// Example: noapi | denied
|
||||
PrivacySandboxCookieDeprecationLabel *string `json:"pscdl,omitempty" mapstructure:"pscdl,omitempty"`
|
||||
// A timestamp measuring the difference between the moment this parameter gets populated and the moment the navigation started on that particular page.
|
||||
TFD *string `json:"tfd,omitempty" mapstructure:"tfd,omitempty"`
|
||||
SST *SST `json:"sst,omitempty" mapstructure:"sst,omitempty"`
|
||||
PAE *string `json:"pae,omitempty" mapstructure:"pae,omitempty"`
|
||||
|
||||
// --- Unresolved ---
|
||||
|
||||
Unknown map[string]any `json:"-" mapstructure:",remain"`
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy-go/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
|
||||
)
|
||||
|
||||
/*
|
||||
AddPaymentInfo https://developers.google.com/tag-platform/gtagjs/reference/events#add_payment_info
|
||||
|
||||
gtag('event', 'add_payment_info', {
|
||||
currency: 'USD',
|
||||
value: 30.03,
|
||||
coupon: 'SUMMER_FUN',
|
||||
payment_type: 'Credit Card',
|
||||
items: [
|
||||
{
|
||||
item_id: 'SKU_12345',
|
||||
item_name: 'Stan and Friends Tee',
|
||||
affiliation: 'Google Store',
|
||||
coupon: 'SUMMER_FUN',
|
||||
discount: 2.22,
|
||||
index: 5,
|
||||
item_brand: 'Google',
|
||||
item_category: 'Apparel',
|
||||
item_category2: 'Adult',
|
||||
item_category3: 'Shirts',
|
||||
item_category4: 'Crew',
|
||||
item_category5: 'Short sleeve',
|
||||
item_list_id: 'related_products',
|
||||
item_list_name: 'Related products',
|
||||
item_variant: 'green',
|
||||
location_id: 'ChIJIQBpAG2ahYAR_6128GcTUEo',
|
||||
price: 10.01,
|
||||
quantity: 3
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
Query: v=2&tid=G-123456>m=45je42t1v9177778896z89175355532za200&_p=1709325262551&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=1220643501.1708014725&ul=en-us&sr=3840x1600&_fplc=0&ur=DE-BY&uaa=arm&uab=64&uafvl=Chromium%3B122.0.6261.69%7CNot(A%253ABrand%3B24.0.0.0%7CGoogle%2520Chrome%3B122.0.6261.69&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pscdl=noapi&_eu=IA&sst.uc=DE&sst.etld=google.de&sst.gcsub=region1&sst.gcd=13l3l3l3l1&sst.tft=1709325262551&_s=5&cu=USD&sid=1709445696&sct=8&seg=0&dl=https%3A%2F%2Fsniffer.local.bestbytes.net%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=add_payment_info&_ss=1&pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Store~cpSUMMER_FUN~ds2.22~lp5~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr10.01~qt3&epn.value=30.03&ep.coupon=SUMMER_FUN&ep.payment_type=Credit%20Card&tfd=120434230&richsstsse
|
||||
*/
|
||||
type AddPaymentInfo struct {
|
||||
Currency string `json:"currency,omitempty"`
|
||||
Value float64 `json:"value,omitempty"`
|
||||
Coupon string `json:"coupon,omitempty"`
|
||||
PaymentType string `json:"payment_type,omitempty"`
|
||||
Items []*Item `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
func (e *AddPaymentInfo) MarshalMPv2() (*mpv2.Event, error) {
|
||||
items := make([]*mpv2.Item, len(e.Items))
|
||||
for i, item := range e.Items {
|
||||
items[i] = item.MarshalMPv2()
|
||||
}
|
||||
eventParameter := map[string]string{}
|
||||
mp.AddStringMap(eventParameter, mpv2.EventParameterCoupon.String(), mp.SetString(e.Coupon))
|
||||
mp.AddStringMap(eventParameter, mpv2.EventParameterPaymentType.String(), mp.SetString(e.PaymentType))
|
||||
eventParameterNumber := map[string]string{}
|
||||
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberValue.String(), mp.SetFloat64(e.Value))
|
||||
return &mpv2.Event{
|
||||
EventName: mp.Set(mpv2.EventNameAddPaymentInfo),
|
||||
Currency: mp.SetString(e.Currency),
|
||||
EventParameter: mp.SetStringMap(eventParameter),
|
||||
EventParameterNumber: mp.SetStringMap(eventParameterNumber),
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy-go/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
|
||||
)
|
||||
|
||||
/*
|
||||
AddShippingInfo https://developers.google.com/tag-platform/gtagjs/reference/events#add_shipping_info
|
||||
|
||||
gtag('event', 'add_payment_info', {
|
||||
currency: 'USD',
|
||||
value: 30.03,
|
||||
coupon: 'SUMMER_FUN',
|
||||
shipping_tier: 'Ground',
|
||||
items: [
|
||||
{
|
||||
item_id: 'SKU_12345',
|
||||
item_name: 'Stan and Friends Tee',
|
||||
affiliation: 'Google Store',
|
||||
coupon: 'SUMMER_FUN',
|
||||
discount: 2.22,
|
||||
index: 5,
|
||||
item_brand: 'Google',
|
||||
item_category: 'Apparel',
|
||||
item_category2: 'Adult',
|
||||
item_category3: 'Shirts',
|
||||
item_category4: 'Crew',
|
||||
item_category5: 'Short sleeve',
|
||||
item_list_id: 'related_products',
|
||||
item_list_name: 'Related products',
|
||||
item_variant: 'green',
|
||||
location_id: 'ChIJIQBpAG2ahYAR_6128GcTUEo',
|
||||
price: 10.01,
|
||||
quantity: 3
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
Query: v=2&tid=G-PZ5ELRCR31>m=45je42t1v9177778896z89175355532za220&_p=1709357665402&_dbg=1&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=1220643501.1708014725&ul=en-us&sr=3840x1600&_fplc=0&ur=DE-BY&uaa=arm&uab=64&uafvl=Chromium%3B122.0.6261.69%7CNot(A%253ABrand%3B24.0.0.0%7CGoogle%2520Chrome%3B122.0.6261.69&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pscdl=noapi&_eu=IA&sst.uc=DE&sst.etld=google.de&sst.gcsub=region1&sst.gcd=13l3l3l3l1&sst.tft=1709357665402&_s=10&cu=USD&sid=1709529821&sct=9&seg=0&dl=https%3A%2F%2Fsniffer.local.bestbytes.net%2F%3Fgtm_debug%3D1709357665301&dr=https%3A%2F%2Ftagassistant.google.com%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=add_payment_info&_ss=1&pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Store~cpSUMMER_FUN~ds2.22~lp5~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr10.01~qt3&epn.value=30.03&ep.coupon=SUMMER_FUN&ep.shipping_tier=Ground&tfd=137238301&richsstsse
|
||||
*/
|
||||
type AddShippingInfo struct {
|
||||
Currency string `json:"currency,omitempty"`
|
||||
Value float64 `json:"value,omitempty"`
|
||||
Coupon string `json:"coupon,omitempty"`
|
||||
ShippingTier string `json:"shipping_tier,omitempty"`
|
||||
Items []*Item `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
func (e *AddShippingInfo) MarshalMPv2() (*mpv2.Event, error) {
|
||||
items := make([]*mpv2.Item, len(e.Items))
|
||||
for i, item := range e.Items {
|
||||
items[i] = item.MarshalMPv2()
|
||||
}
|
||||
eventParameter := map[string]string{}
|
||||
mp.AddStringMap(eventParameter, mpv2.EventParameterCoupon.String(), mp.SetString(e.Coupon))
|
||||
mp.AddStringMap(eventParameter, mpv2.EventParameterShippingTier.String(), mp.SetString(e.ShippingTier))
|
||||
eventParameterNumber := map[string]string{}
|
||||
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberValue.String(), mp.SetFloat64(e.Value))
|
||||
return &mpv2.Event{
|
||||
EventName: mp.Set(mpv2.EventNameAddShippingInfo),
|
||||
Currency: mp.SetString(e.Currency),
|
||||
EventParameter: mp.SetStringMap(eventParameter),
|
||||
EventParameterNumber: mp.SetStringMap(eventParameterNumber),
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
@ -1,60 +0,0 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy-go/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
|
||||
)
|
||||
|
||||
/*
|
||||
AddToCart https://developers.google.com/tag-platform/gtagjs/reference/events#add_to_cart
|
||||
|
||||
gtag('event', 'add_to_cart', {
|
||||
currency: 'USD',
|
||||
value: 30.03,
|
||||
items: [
|
||||
{
|
||||
item_id: 'SKU_12345',
|
||||
item_name: 'Stan and Friends Tee',
|
||||
affiliation: 'Google Store',
|
||||
coupon: 'SUMMER_FUN',
|
||||
discount: 2.22,
|
||||
index: 5,
|
||||
item_brand: 'Google',
|
||||
item_category: 'Apparel',
|
||||
item_category2: 'Adult',
|
||||
item_category3: 'Shirts',
|
||||
item_category4: 'Crew',
|
||||
item_category5: 'Short sleeve',
|
||||
item_list_id: 'related_products',
|
||||
item_list_name: 'Related products',
|
||||
item_variant: 'green',
|
||||
location_id: 'ChIJIQBpAG2ahYAR_6128GcTUEo',
|
||||
price: 10.01,
|
||||
quantity: 3
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
Query: v=2&tid=G-PZ5ELRCR31>m=45je42t1v9177778896z89175355532za200&_p=1709325262551&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=1220643501.1708014725&ul=en-us&sr=3840x1600&_fplc=0&ur=DE-BY&uaa=arm&uab=64&uafvl=Chromium%3B122.0.6261.69%7CNot(A%253ABrand%3B24.0.0.0%7CGoogle%2520Chrome%3B122.0.6261.69&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pscdl=noapi&_eu=IA&sst.uc=DE&sst.etld=google.de&sst.gcsub=region1&sst.gcd=13l3l3l3l1&sst.tft=1709325262551&_s=3&cu=USD&sid=1709324719&sct=6&seg=1&dl=https%3A%2F%2Fsniffer.local.bestbytes.net%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=add_to_cart&pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Store~cpSUMMER_FUN~ds2.22~lp5~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr10.01~qt3&epn.value=30.03&_et=3187&tfd=11387&richsstsse
|
||||
*/
|
||||
type AddToCart struct {
|
||||
Currency string `json:"currency,omitempty"`
|
||||
Value float64 `json:"value,omitempty"`
|
||||
Items []*Item `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
func (e *AddToCart) MarshalMPv2() (*mpv2.Event, error) {
|
||||
items := make([]*mpv2.Item, len(e.Items))
|
||||
for i, item := range e.Items {
|
||||
items[i] = item.MarshalMPv2()
|
||||
}
|
||||
eventParameterNumber := map[string]string{}
|
||||
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberValue.String(), mp.SetFloat64(e.Value))
|
||||
return &mpv2.Event{
|
||||
EventName: mp.Set(mpv2.EventNameAddToCart),
|
||||
Currency: mp.SetString(e.Currency),
|
||||
EventParameterNumber: mp.SetStringMap(eventParameterNumber),
|
||||
Items: items,
|
||||
Richsstsse: mp.Set(""),
|
||||
}, nil
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy-go/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
|
||||
)
|
||||
|
||||
/*
|
||||
AddToWishlist https://developers.google.com/tag-platform/gtagjs/reference/events#add_to_wishlist
|
||||
|
||||
gtag('event', 'add_to_wishlist', {
|
||||
currency: 'USD',
|
||||
value: 30.03,
|
||||
items: [
|
||||
{
|
||||
item_id: 'SKU_12345',
|
||||
item_name: 'Stan and Friends Tee',
|
||||
affiliation: 'Google Store',
|
||||
coupon: 'SUMMER_FUN',
|
||||
discount: 2.22,
|
||||
index: 5,
|
||||
item_brand: 'Google',
|
||||
item_category: 'Apparel',
|
||||
item_category2: 'Adult',
|
||||
item_category3: 'Shirts',
|
||||
item_category4: 'Crew',
|
||||
item_category5: 'Short sleeve',
|
||||
item_list_id: 'related_products',
|
||||
item_list_name: 'Related products',
|
||||
item_variant: 'green',
|
||||
location_id: 'ChIJIQBpAG2ahYAR_6128GcTUEo',
|
||||
price: 10.01,
|
||||
quantity: 3
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
Query: v=2&tid=G-PZ5ELRCR31>m=45je42t1v9177778896z89175355532za200&_p=1709325262551&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=1220643501.1708014725&ul=en-us&sr=3840x1600&_fplc=0&ur=DE-BY&uaa=arm&uab=64&uafvl=Chromium%3B122.0.6261.69%7CNot(A%253ABrand%3B24.0.0.0%7CGoogle%2520Chrome%3B122.0.6261.69&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pscdl=noapi&_eu=IA&sst.uc=DE&sst.etld=google.de&sst.gcsub=region1&sst.gcd=13l3l3l3l1&sst.tft=1709325262551&_s=3&cu=USD&sid=1709324719&sct=6&seg=1&dl=https%3A%2F%2Fsniffer.local.bestbytes.net%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=add_to_wishlist&pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Store~cpSUMMER_FUN~ds2.22~lp5~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr10.01~qt3&epn.value=30.03&_et=3187&tfd=11387&richsstsse
|
||||
*/
|
||||
type AddToWishlist struct {
|
||||
Currency string `json:"currency,omitempty"`
|
||||
Value float64 `json:"value,omitempty"`
|
||||
Items []*Item `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
func (e *AddToWishlist) MarshalMPv2() (*mpv2.Event, error) {
|
||||
items := make([]*mpv2.Item, len(e.Items))
|
||||
for i, item := range e.Items {
|
||||
items[i] = item.MarshalMPv2()
|
||||
}
|
||||
eventParameterNumber := map[string]string{}
|
||||
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberValue.String(), mp.SetFloat64(e.Value))
|
||||
return &mpv2.Event{
|
||||
EventName: mp.Set(mpv2.EventNameAddToWishlist),
|
||||
Currency: mp.SetString(e.Currency),
|
||||
EventParameterNumber: mp.SetStringMap(eventParameterNumber),
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
@ -1,64 +0,0 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy-go/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
|
||||
)
|
||||
|
||||
/*
|
||||
BeginCheckout https://developers.google.com/tag-platform/gtagjs/reference/events#begin_checkout
|
||||
|
||||
gtag('event', 'begin_checkout', {
|
||||
currency: 'USD',
|
||||
value: 30.03,
|
||||
coupon: 'SUMMER_FUN',
|
||||
items: [
|
||||
{
|
||||
item_id: 'SKU_12345',
|
||||
item_name: 'Stan and Friends Tee',
|
||||
affiliation: 'Google Store',
|
||||
coupon: 'SUMMER_FUN',
|
||||
discount: 2.22,
|
||||
index: 5,
|
||||
item_brand: 'Google',
|
||||
item_category: 'Apparel',
|
||||
item_category2: 'Adult',
|
||||
item_category3: 'Shirts',
|
||||
item_category4: 'Crew',
|
||||
item_category5: 'Short sleeve',
|
||||
item_list_id: 'related_products',
|
||||
item_list_name: 'Related products',
|
||||
item_variant: 'green',
|
||||
location_id: 'ChIJIQBpAG2ahYAR_6128GcTUEo',
|
||||
price: 10.01,
|
||||
quantity: 3
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
Query: v=2&tid=G-123456>m=45je42t1v9177778896z89175355532za200&_p=1709325262551&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=1220643501.1708014725&ul=en-us&sr=3840x1600&_fplc=0&ur=DE-BY&uaa=arm&uab=64&uafvl=Chromium%3B122.0.6261.69%7CNot(A%253ABrand%3B24.0.0.0%7CGoogle%2520Chrome%3B122.0.6261.69&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pscdl=noapi&_eu=IA&sst.uc=DE&sst.etld=google.de&sst.gcsub=region1&sst.gcd=13l3l3l3l1&sst.tft=1709325262551&_s=5&cu=USD&sid=1709445696&sct=8&seg=0&dl=https%3A%2F%2Fsniffer.local.bestbytes.net%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=begin_checkout&_ss=1&pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Store~cpSUMMER_FUN~ds2.22~lp5~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr10.01~qt3&epn.value=30.03&ep.coupon=SUMMER_FUN&tfd=120434230&richsstsse
|
||||
*/
|
||||
type BeginCheckout struct {
|
||||
Currency string `json:"currency,omitempty"`
|
||||
Value float64 `json:"value,omitempty"`
|
||||
Coupon string `json:"coupon,omitempty"`
|
||||
Items []*Item `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
func (e *BeginCheckout) MarshalMPv2() (*mpv2.Event, error) {
|
||||
items := make([]*mpv2.Item, len(e.Items))
|
||||
for i, item := range e.Items {
|
||||
items[i] = item.MarshalMPv2()
|
||||
}
|
||||
eventParameter := map[string]string{}
|
||||
mp.AddStringMap(eventParameter, mpv2.EventParameterCoupon.String(), mp.SetString(e.Coupon))
|
||||
eventParameterNumber := map[string]string{}
|
||||
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberValue.String(), mp.SetFloat64(e.Value))
|
||||
return &mpv2.Event{
|
||||
EventName: mp.Set(mpv2.EventNameBeginCheckout),
|
||||
Currency: mp.SetString(e.Currency),
|
||||
EventParameter: mp.SetStringMap(eventParameter),
|
||||
EventParameterNumber: mp.SetStringMap(eventParameterNumber),
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy-go/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
|
||||
)
|
||||
|
||||
type Item struct {
|
||||
Affiliation string `json:"affiliation,omitempty"`
|
||||
Coupon string `json:"coupon,omitempty"`
|
||||
Discount float64 `json:"discount,omitempty"`
|
||||
Index int `json:"index,omitempty"`
|
||||
ItemBrand string `json:"item_brand,omitempty"`
|
||||
ItemCategory string `json:"item_category,omitempty"`
|
||||
ItemCategory2 string `json:"item_category2,omitempty"`
|
||||
ItemCategory3 string `json:"item_category3,omitempty"`
|
||||
ItemCategory4 string `json:"item_category4,omitempty"`
|
||||
ItemCategory5 string `json:"item_category5,omitempty"`
|
||||
ItemID string `json:"item_id,omitempty"`
|
||||
ItemListName string `json:"item_list_name,omitempty"`
|
||||
ItemName string `json:"item_name,omitempty"`
|
||||
ItemVariant string `json:"item_variant,omitempty"`
|
||||
ItemListID string `json:"listId,omitempty"`
|
||||
LocationID string `json:"location_id,omitempty"`
|
||||
Price string `json:"price,omitempty"`
|
||||
Quantity float64 `json:"quantity,omitempty"`
|
||||
}
|
||||
|
||||
func (e *Item) MarshalMPv2() *mpv2.Item {
|
||||
return &mpv2.Item{
|
||||
ID: mp.SetString(e.ItemID),
|
||||
Name: mp.SetString(e.ItemName),
|
||||
Brand: mp.SetString(e.ItemBrand),
|
||||
CategoryHierarchy1: mp.SetString(e.ItemCategory),
|
||||
CategoryHierarchy2: mp.SetString(e.ItemCategory2),
|
||||
CategoryHierarchy3: mp.SetString(e.ItemCategory3),
|
||||
CategoryHierarchy4: mp.SetString(e.ItemCategory4),
|
||||
CategoryHierarchy5: mp.SetString(e.ItemCategory5),
|
||||
Price: mp.SetString(e.Price),
|
||||
Quantity: mp.SetFloat64(e.Quantity),
|
||||
Variant: mp.SetString(e.ItemVariant),
|
||||
Coupon: mp.SetString(e.Coupon),
|
||||
Discount: mp.SetFloat64(e.Discount),
|
||||
ListName: mp.SetString(e.ItemListName),
|
||||
ListID: mp.SetString(e.ItemListID),
|
||||
ListPosition: mp.SetInt(e.Index),
|
||||
Affiliation: mp.SetString(e.Affiliation),
|
||||
LocationID: mp.SetString(e.LocationID),
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy-go/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
|
||||
)
|
||||
|
||||
/*
|
||||
Login - Send this event to signify that a user has logged in to your website or app.
|
||||
|
||||
gtag('event', 'login', {
|
||||
method: 'Google'
|
||||
});
|
||||
|
||||
Reference: https://developers.google.com/tag-platform/gtagjs/reference/events#login
|
||||
*/
|
||||
type Login struct {
|
||||
Method string `json:"method,omitempty"`
|
||||
}
|
||||
|
||||
func (e *Login) MarshalMPv2() (*mpv2.Event, error) {
|
||||
eventParameter := map[string]string{}
|
||||
mp.AddStringMap(eventParameter, mpv2.EventParameterMethod.String(), mp.SetString(e.Method))
|
||||
return &mpv2.Event{
|
||||
EventName: mp.Set(mpv2.EventNameLogin),
|
||||
EventParameter: mp.SetStringMap(eventParameter),
|
||||
}, nil
|
||||
}
|
||||
@ -1,73 +0,0 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy-go/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
|
||||
)
|
||||
|
||||
/*
|
||||
Purchase https://developers.google.com/tag-platform/gtagjs/reference/events#purchase
|
||||
|
||||
gtag('event', 'purchase', {
|
||||
currency: 'USD',
|
||||
value: 30.03,
|
||||
transaction_id: 'T_12345',
|
||||
coupon: 'SUMMER_FUN',
|
||||
shipping: 3.33,
|
||||
tax: 1.11,
|
||||
items: [
|
||||
{
|
||||
item_id: 'SKU_12345',
|
||||
item_name: 'Stan and Friends Tee',
|
||||
affiliation: 'Google Store',
|
||||
coupon: 'SUMMER_FUN',
|
||||
discount: 2.22,
|
||||
index: 5,
|
||||
item_brand: 'Google',
|
||||
item_category: 'Apparel',
|
||||
item_category2: 'Adult',
|
||||
item_category3: 'Shirts',
|
||||
item_category4: 'Crew',
|
||||
item_category5: 'Short sleeve',
|
||||
item_list_id: 'related_products',
|
||||
item_list_name: 'Related products',
|
||||
item_variant: 'green',
|
||||
location_id: 'ChIJIQBpAG2ahYAR_6128GcTUEo',
|
||||
price: 10.01,
|
||||
quantity: 3
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
Query: v=2&tid=G-PZ5ELRCR31>m=45he42t1v9177778896z89175355532za220&_p=1709537217366&_dbg=1&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=1220643501.1708014725&ul=en-us&sr=3840x1600&_fplc=0&ur=&uaa=arm&uab=64&uafvl=Chromium%3B122.0.6261.69%7CNot(A%253ABrand%3B24.0.0.0%7CGoogle%2520Chrome%3B122.0.6261.69&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pscdl=noapi&_eu=IA&sst.uc=&sst.gcd=13l3l3l3l1&sst.tft=1709537217366&_s=6&cu=USD&sid=1709534872&sct=10&seg=1&dl=https%3A%2F%2Fsniffer.local.bestbytes.net%2F%3Fgtm_debug%3D1709537217296&dr=https%3A%2F%2Ftagassistant.google.com%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=purchase&_c=1&pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Store~cpSUMMER_FUN~ds2.22~lp5~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr10.01~qt3&epn.value=30.03&ep.transaction_id=T_12345&ep.coupon=SUMMER_FUN&epn.shipping=3.33&epn.tax=1.11&tfd=184622&richsstsse
|
||||
*/
|
||||
type Purchase struct {
|
||||
Currency string `json:"currency,omitempty"`
|
||||
Value float64 `json:"value,omitempty"`
|
||||
TransactionID string `json:"transaction_id,omitempty"`
|
||||
Coupon string `json:"coupon,omitempty"`
|
||||
Shipping float64 `json:"shipping,omitempty"`
|
||||
Tax float64 `json:"tax,omitempty"`
|
||||
Items []*Item `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
func (e *Purchase) MarshalMPv2() (*mpv2.Event, error) {
|
||||
items := make([]*mpv2.Item, len(e.Items))
|
||||
for i, item := range e.Items {
|
||||
items[i] = item.MarshalMPv2()
|
||||
}
|
||||
eventParameter := map[string]string{}
|
||||
mp.AddStringMap(eventParameter, mpv2.EventParameterTransactionID.String(), mp.SetString(e.TransactionID))
|
||||
mp.AddStringMap(eventParameter, mpv2.EventParameterCoupon.String(), mp.SetString(e.Coupon))
|
||||
eventParameterNumber := map[string]string{}
|
||||
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberValue.String(), mp.SetFloat64(e.Value))
|
||||
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberShipping.String(), mp.SetFloat64(e.Shipping))
|
||||
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberTax.String(), mp.SetFloat64(e.Tax))
|
||||
return &mpv2.Event{
|
||||
EventName: mp.Set(mpv2.EventNamePurchase),
|
||||
Currency: mp.SetString(e.Currency),
|
||||
EventParameter: mp.SetStringMap(eventParameter),
|
||||
EventParameterNumber: mp.SetStringMap(eventParameterNumber),
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
@ -1,73 +0,0 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy-go/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
|
||||
)
|
||||
|
||||
/*
|
||||
Refund https://developers.google.com/tag-platform/gtagjs/reference/events#refund
|
||||
|
||||
gtag('event', 'refund', {
|
||||
currency: 'USD',
|
||||
value: 30.03,
|
||||
transaction_id: 'T_12345',
|
||||
coupon: 'SUMMER_FUN',
|
||||
shipping: 3.33,
|
||||
tax: 1.11,
|
||||
items: [
|
||||
{
|
||||
item_id: 'SKU_12345',
|
||||
item_name: 'Stan and Friends Tee',
|
||||
affiliation: 'Google Store',
|
||||
coupon: 'SUMMER_FUN',
|
||||
discount: 2.22,
|
||||
index: 5,
|
||||
item_brand: 'Google',
|
||||
item_category: 'Apparel',
|
||||
item_category2: 'Adult',
|
||||
item_category3: 'Shirts',
|
||||
item_category4: 'Crew',
|
||||
item_category5: 'Short sleeve',
|
||||
item_list_id: 'related_products',
|
||||
item_list_name: 'Related products',
|
||||
item_variant: 'green',
|
||||
location_id: 'ChIJIQBpAG2ahYAR_6128GcTUEo',
|
||||
price: 10.01,
|
||||
quantity: 3
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
Query: v=2&tid=G-PZ5ELRCR31>m=45he42t1v9177778896z89175355532za220&_p=1709537217366&_dbg=1&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=1220643501.1708014725&ul=en-us&sr=3840x1600&_fplc=0&ur=&uaa=arm&uab=64&uafvl=Chromium%3B122.0.6261.69%7CNot(A%253ABrand%3B24.0.0.0%7CGoogle%2520Chrome%3B122.0.6261.69&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pscdl=noapi&_eu=IA&sst.uc=&sst.gcd=13l3l3l3l1&sst.tft=1709537217366&_s=6&cu=USD&sid=1709534872&sct=10&seg=1&dl=https%3A%2F%2Fsniffer.local.bestbytes.net%2F%3Fgtm_debug%3D1709537217296&dr=https%3A%2F%2Ftagassistant.google.com%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=refund&_c=1&pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Store~cpSUMMER_FUN~ds2.22~lp5~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr10.01~qt3&epn.value=30.03&ep.transaction_id=T_12345&ep.coupon=SUMMER_FUN&epn.shipping=3.33&epn.tax=1.11&tfd=184622&richsstsse
|
||||
*/
|
||||
type Refund struct {
|
||||
Currency string `json:"currency,omitempty"`
|
||||
Value float64 `json:"value,omitempty"`
|
||||
TransactionID string `json:"transaction_id,omitempty"`
|
||||
Coupon string `json:"coupon,omitempty"`
|
||||
Shipping float64 `json:"shipping,omitempty"`
|
||||
Tax float64 `json:"tax,omitempty"`
|
||||
Items []*Item `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
func (e *Refund) MarshalMPv2() (*mpv2.Event, error) {
|
||||
items := make([]*mpv2.Item, len(e.Items))
|
||||
for i, item := range e.Items {
|
||||
items[i] = item.MarshalMPv2()
|
||||
}
|
||||
eventParameter := map[string]string{}
|
||||
mp.AddStringMap(eventParameter, mpv2.EventParameterTransactionID.String(), mp.SetString(e.TransactionID))
|
||||
mp.AddStringMap(eventParameter, mpv2.EventParameterCoupon.String(), mp.SetString(e.Coupon))
|
||||
eventParameterNumber := map[string]string{}
|
||||
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberValue.String(), mp.SetFloat64(e.Value))
|
||||
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberShipping.String(), mp.SetFloat64(e.Shipping))
|
||||
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberTax.String(), mp.SetFloat64(e.Tax))
|
||||
return &mpv2.Event{
|
||||
EventName: mp.Set(mpv2.EventNameRefund),
|
||||
Currency: mp.SetString(e.Currency),
|
||||
EventParameter: mp.SetStringMap(eventParameter),
|
||||
EventParameterNumber: mp.SetStringMap(eventParameterNumber),
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy-go/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
|
||||
)
|
||||
|
||||
/*
|
||||
RemoveFromCart https://developers.google.com/tag-platform/gtagjs/reference/events#remove_from_cart
|
||||
|
||||
gtag('event', 'remove_from_cart', {
|
||||
currency: 'USD',
|
||||
value: 30.03,
|
||||
items: [
|
||||
{
|
||||
item_id: 'SKU_12345',
|
||||
item_name: 'Stan and Friends Tee',
|
||||
affiliation: 'Google Store',
|
||||
coupon: 'SUMMER_FUN',
|
||||
discount: 2.22,
|
||||
index: 5,
|
||||
item_brand: 'Google',
|
||||
item_category: 'Apparel',
|
||||
item_category2: 'Adult',
|
||||
item_category3: 'Shirts',
|
||||
item_category4: 'Crew',
|
||||
item_category5: 'Short sleeve',
|
||||
item_list_id: 'related_products',
|
||||
item_list_name: 'Related products',
|
||||
item_variant: 'green',
|
||||
location_id: 'ChIJIQBpAG2ahYAR_6128GcTUEo',
|
||||
price: 10.01,
|
||||
quantity: 3
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
Query: v=2&tid=G-PZ5ELRCR31>m=45je42t1v9177778896z89175355532za200&_p=1709325262551&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=1220643501.1708014725&ul=en-us&sr=3840x1600&_fplc=0&ur=DE-BY&uaa=arm&uab=64&uafvl=Chromium%3B122.0.6261.69%7CNot(A%253ABrand%3B24.0.0.0%7CGoogle%2520Chrome%3B122.0.6261.69&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pscdl=noapi&_eu=IA&sst.uc=DE&sst.etld=google.de&sst.gcsub=region1&sst.gcd=13l3l3l3l1&sst.tft=1709325262551&_s=3&cu=USD&sid=1709324719&sct=6&seg=1&dl=https%3A%2F%2Fsniffer.local.bestbytes.net%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=remove_from_cart&pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Store~cpSUMMER_FUN~ds2.22~lp5~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr10.01~qt3&epn.value=30.03&_et=3187&tfd=11387&richsstsse
|
||||
*/
|
||||
type RemoveFromCart struct {
|
||||
Currency string `json:"currency,omitempty"`
|
||||
Value float64 `json:"value,omitempty"`
|
||||
Items []*Item `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
func (e *RemoveFromCart) MarshalMPv2() (*mpv2.Event, error) {
|
||||
items := make([]*mpv2.Item, len(e.Items))
|
||||
for i, item := range e.Items {
|
||||
items[i] = item.MarshalMPv2()
|
||||
}
|
||||
eventParameterNumber := map[string]string{}
|
||||
mp.AddStringMap(eventParameterNumber, mpv2.EventParameterNumberValue.String(), mp.SetFloat64(e.Value))
|
||||
return &mpv2.Event{
|
||||
EventName: mp.Set(mpv2.EventNameRemoveFromCart),
|
||||
Currency: mp.SetString(e.Currency),
|
||||
EventParameterNumber: mp.SetStringMap(eventParameterNumber),
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy-go/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
|
||||
)
|
||||
|
||||
/*
|
||||
Search https://developers.google.com/tag-platform/gtagjs/reference/events#search
|
||||
|
||||
gtag('event', 'search', {
|
||||
search_term: 't-shirts',
|
||||
});
|
||||
|
||||
Query: v=2&tid=G-PZ5ELRCR31>m=45je42t1v9177778896z89175355532za200&_p=1709325262551&gcd=13l3l3l3l1&npa=0&dma_cps=sypham&dma=1&cid=1220643501.1708014725&ul=en-us&sr=3840x1600&_fplc=0&ur=DE-BY&uaa=arm&uab=64&uafvl=Chromium%3B122.0.6261.69%7CNot(A%253ABrand%3B24.0.0.0%7CGoogle%2520Chrome%3B122.0.6261.69&uamb=0&uam=&uap=macOS&uapv=14.3.1&uaw=0&are=1&pscdl=noapi&_eu=IA&sst.uc=DE&sst.etld=google.de&sst.gcsub=region1&sst.gcd=13l3l3l3l1&sst.tft=1709325262551&_s=3&cu=USD&sid=1709324719&sct=6&seg=1&dl=https%3A%2F%2Fsniffer.local.bestbytes.net%2F&dt=Server%20Side%20Tracking%20Prototype%20(codename%3A%20sniffer)&en=search&pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Store~cpSUMMER_FUN~ds2.22~lp5~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr10.01~qt3&epn.value=30.03&_et=3187&tfd=11387&richsstsse
|
||||
*/
|
||||
type Search struct {
|
||||
SearchTerm string `json:"search_term,omitempty"`
|
||||
}
|
||||
|
||||
func (e *Search) MarshalMPv2() (*mpv2.Event, error) {
|
||||
eventParameter := map[string]string{}
|
||||
mp.AddStringMap(eventParameter, mpv2.EventParameterSearchTerm.String(), mp.SetString(e.SearchTerm))
|
||||
return &mpv2.Event{
|
||||
EventName: mp.Set(mpv2.EventNameSearch),
|
||||
EventParameter: mp.SetStringMap(eventParameter),
|
||||
}, nil
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy-go/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy-go/measurementprotocol/v2"
|
||||
)
|
||||
|
||||
/*
|
||||
SignUp - Send this event to signify that a user has logged in to your website or app.
|
||||
|
||||
gtag('event', 'sign_up', {
|
||||
method: 'Google'
|
||||
});
|
||||
|
||||
Reference: https://developers.google.com/tag-platform/gtagjs/reference/events#sign_up
|
||||
*/
|
||||
type SignUp struct {
|
||||
Method string `json:"method,omitempty"`
|
||||
}
|
||||
|
||||
func (e *SignUp) MarshalMPv2() (*mpv2.Event, error) {
|
||||
eventParameter := map[string]string{}
|
||||
mp.AddStringMap(eventParameter, mpv2.EventParameterMethod.String(), mp.SetString(e.Method))
|
||||
return &mpv2.Event{
|
||||
EventName: mp.Set(mpv2.EventNameSignUp),
|
||||
EventParameter: mp.SetStringMap(eventParameter),
|
||||
}, nil
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
package v2
|
||||
|
||||
type EventName string
|
||||
|
||||
const (
|
||||
EventNameAddPaymentInfo EventName = "add_payment_info"
|
||||
EventNameAddShippingInfo EventName = "add_shipping_info"
|
||||
EventNameAddToCart EventName = "add_to_cart"
|
||||
EventNameAddToWishlist EventName = "add_to_wishlit"
|
||||
EventNameBeginCheckout EventName = "begin_checkout"
|
||||
EventNameEarnVirtualMoney EventName = "earn_virtual_money"
|
||||
EventNameGenerateLead EventName = "generate_lead"
|
||||
EventNameJoinGroup EventName = "join_group"
|
||||
EventNameLevelEnd EventName = "level_end"
|
||||
EventNameLevelStart EventName = "level_start"
|
||||
EventNameLevelUp EventName = "level_up"
|
||||
EventNameLogin EventName = "login"
|
||||
EventNamePostScore EventName = "post_score"
|
||||
EventNamePurchase EventName = "purchase"
|
||||
EventNameRefund EventName = "refund"
|
||||
EventNameRemoveFromCart EventName = "remove_from_cart"
|
||||
EventNameSearch EventName = "search"
|
||||
EventNameSelectContent EventName = "select_content"
|
||||
EventNameSelectItem EventName = "select_item"
|
||||
EventNameShare EventName = "share"
|
||||
EventNameSignUp EventName = "sign_up"
|
||||
EventNameSpendVirtualMoney EventName = "spend_virtual_money"
|
||||
EventNameTutorialBegin EventName = "tutorial_begin"
|
||||
EventNameTuturialComplete EventName = "tutorial_complete"
|
||||
EventNameUnlockArchievement EventName = "unlock_archievement"
|
||||
EventNameViewCart EventName = "view_cart"
|
||||
EventNameViewItem EventName = "view_item"
|
||||
EventNameViewItemList EventName = "view_item_list"
|
||||
EventNameViewPromotion EventName = "view_promotion"
|
||||
)
|
||||
|
||||
func (s EventName) String() string {
|
||||
return string(s)
|
||||
}
|
||||
@ -1,164 +0,0 @@
|
||||
package v2
|
||||
|
||||
// EventParameter as string
|
||||
// See https://support.google.com/analytics/table/13594742?sjid=7861230991468479976-EU
|
||||
type EventParameter string
|
||||
|
||||
const (
|
||||
// EventParameterAppVersion The mobile app's versionName (Android) or short bundle version (iOS) in which an event occurred
|
||||
EventParameterAppVersion EventParameter = "app_version"
|
||||
// EventParameterCancellationReason The reason a cancellation occurred
|
||||
EventParameterCancellationReason EventParameter = "cancellation_reason"
|
||||
// EventParameterFirebaseErrorValue Additional details corresponding to the error code reported by the Firebase SDK
|
||||
EventParameterFirebaseErrorValue EventParameter = "firebase_error_value"
|
||||
// EventParameterFirebaseScreen The current screen’s name, as provided by the developer
|
||||
EventParameterFirebaseScreen EventParameter = "firebase_screen"
|
||||
// EventParameterFirebaseScreenClass The current screen’s class name
|
||||
EventParameterFirebaseScreenClass EventParameter = "firebase_screen_class"
|
||||
// EventParameterFirebaseScreenId A random identifier for the current screen
|
||||
EventParameterFirebaseScreenId EventParameter = "firebase_screen_id"
|
||||
// EventParameterMessageId The Firebase Cloud Messaging or Firebase In-App Messaging message identifier, which is unique per message campaign
|
||||
EventParameterMessageId EventParameter = "message_id"
|
||||
// EventParameterMessageName The Firebase Cloud Messaging or Firebase In-App Messaging message name
|
||||
EventParameterMessageName EventParameter = "message_name"
|
||||
// EventParameterMessageType The Firebase Cloud Messaging message notification type
|
||||
EventParameterMessageType EventParameter = "message_type"
|
||||
// EventParameterPreviousAppVersion For the app_update event, the parameter signifies the previous application version
|
||||
EventParameterPreviousAppVersion EventParameter = "previous_app_version"
|
||||
// EventParameterPreviousOsVersion For the os_update event, the parameter signifies the previous OS version
|
||||
EventParameterPreviousOsVersion EventParameter = "previous_os_version"
|
||||
// EventParameterCampaignContent The ad content that was associated with the start of a session
|
||||
EventParameterCampaignContent EventParameter = "campaign_content"
|
||||
// EventParameterCampaignMedium The method for acquiring users to your website or application
|
||||
EventParameterCampaignMedium EventParameter = "campaign_medium"
|
||||
// EventParameterCampaignSource A representation of the publisher or inventory source from which traffic originated. For example, users who return to your website from Google Search show as "google" in the Session source dimension
|
||||
EventParameterCampaignSource EventParameter = "campaign_source"
|
||||
// EventParameterCampaignTerm The term that was associated with the start of a session
|
||||
EventParameterCampaignTerm EventParameter = "campaign_term"
|
||||
// EventParameterCoupon The coupon name or code associated with an event
|
||||
EventParameterCoupon EventParameter = "coupon"
|
||||
// EventParameterCurrency The currency used in an event, in 3-letter ISO 4217 format. For example, the currency used in a purchase
|
||||
EventParameterCurrency EventParameter = "currency"
|
||||
// EventParameterShippingTier The shipping tier selected for delivery of a purchased item
|
||||
EventParameterShippingTier EventParameter = "shipping_tier"
|
||||
// EventParameterTransactionId The unique identifier of a transaction
|
||||
EventParameterTransactionId EventParameter = "transaction_id"
|
||||
// EventParameterValue The monetary value of the event
|
||||
EventParameterValue EventParameter = "value"
|
||||
// EventParameterAffiliation A product affiliation to designate a supplying company or brick and mortar store location
|
||||
EventParameterAffiliation EventParameter = "affiliation"
|
||||
// EventParameterCreativeName The name of a creative used in a promotion. Example value: summer_banner
|
||||
EventParameterCreativeName EventParameter = "creative_name"
|
||||
// EventParameterCreativeSlot The name of the promotional creative slot associated with an event. Example value: featured_app_1
|
||||
EventParameterCreativeSlot EventParameter = "creative_slot"
|
||||
// EventParameterDiscount The value of a discount value associated with a purchased item
|
||||
EventParameterDiscount EventParameter = "discount"
|
||||
// EventParameterIndex The index of the item in a list
|
||||
EventParameterIndex EventParameter = "index"
|
||||
// EventParameterItemBrand The brand of an item
|
||||
EventParameterItemBrand EventParameter = "item_brand"
|
||||
// EventParameterItemCategory The category of an item. If used as part of a category hierarchy or taxonomy, then this is the first category
|
||||
EventParameterItemCategory EventParameter = "item_category"
|
||||
// EventParameterItemCategory2 The second hierarchical category in which you classified an item
|
||||
EventParameterItemCategory2 EventParameter = "item_category2"
|
||||
// EventParameterItemCategory3 The third hierarchical category in which you classified an item
|
||||
EventParameterItemCategory3 EventParameter = "item_category3"
|
||||
// EventParameterItemCategory4 The fourth hierarchical category in which you classified an item
|
||||
EventParameterItemCategory4 EventParameter = "Item_category4"
|
||||
// EventParameterItemCategory5 The fifth hierarchical category in which you classified an item
|
||||
EventParameterItemCategory5 EventParameter = "Item_category5"
|
||||
// EventParameterItemId The ID that you specify for an item
|
||||
EventParameterItemId EventParameter = "item_id"
|
||||
// EventParameterItemListId The name of the list in which an item was presented to a user
|
||||
EventParameterItemListId EventParameter = "item_list_id"
|
||||
// EventParameterItemListName The ID of the list in which an item was presented to a user
|
||||
EventParameterItemListName EventParameter = "item_list_name"
|
||||
// EventParameterItemName The name of the event that contains the parameter group
|
||||
EventParameterItemName EventParameter = "item_name"
|
||||
// EventParameterItemVariant The item variant or unique code or description (e.g., XS, S, M, L for size; Red, Blue, Green, Black for color) for additional item details or options
|
||||
EventParameterItemVariant EventParameter = "item_variant"
|
||||
// EventParameterLocationId The physical location associated with the item (e.g. the physical store location)
|
||||
EventParameterLocationId EventParameter = "location_id"
|
||||
// EventParameterPromotionId The ID of the promotion associated with an event
|
||||
EventParameterPromotionId EventParameter = "promotion_id"
|
||||
// EventParameterPromotionName The name of the promotion associated with an event
|
||||
EventParameterPromotionName EventParameter = "promotion_name"
|
||||
// EventParameterAchievementId The ID of an achievement that was unlocked in a game
|
||||
EventParameterAchievementId EventParameter = "achievement_id"
|
||||
// EventParameterCharacter The name of a character in a game
|
||||
EventParameterCharacter EventParameter = "character"
|
||||
// EventParameterLevelName The name of the level in a game
|
||||
EventParameterLevelName EventParameter = "level_name"
|
||||
// EventParameterVirtualCurrencyName The name of a virtual currency
|
||||
EventParameterVirtualCurrencyName EventParameter = "virtual_currency_name"
|
||||
// EventParameterFileExtension The extension of a file download
|
||||
EventParameterFileExtension EventParameter = "file_extension"
|
||||
// EventParameterFileName The page path of a file download
|
||||
EventParameterFileName EventParameter = "file_name"
|
||||
// EventParameterFormDestination The URL to which a form is being submitted
|
||||
EventParameterFormDestination EventParameter = "form_destination"
|
||||
// EventParameterFormId The HTML id attribution of the <form> DOM element
|
||||
EventParameterFormId EventParameter = "form_id"
|
||||
// EventParameterFormName The HTML name attribute of the <form> DOM element
|
||||
EventParameterFormName EventParameter = "form_name"
|
||||
// EventParameterFormSubmitText The text of the submit button, if present
|
||||
EventParameterFormSubmitText EventParameter = "form_submit_text"
|
||||
// EventParameterGroupId The ID of a group
|
||||
EventParameterGroupId EventParameter = "group_id"
|
||||
// EventParameterLanguage The language setting of a user’s browser or device, displayed as the ISO 639 language code
|
||||
EventParameterLanguage EventParameter = "language"
|
||||
// EventParameterPercentScrolled The percentage down the page that the user scrolled
|
||||
EventParameterPercentScrolled EventParameter = "percent_scrolled"
|
||||
// EventParameterSearchTerm The strings or keywords used in a search
|
||||
EventParameterSearchTerm EventParameter = "search_term"
|
||||
// EventParameterLinkClasses The HTML class attribute for an outbound link or file download
|
||||
EventParameterLinkClasses EventParameter = "link_classes"
|
||||
// EventParameterLinkDomain The destination domain of an outbound link or file download
|
||||
EventParameterLinkDomain EventParameter = "link_domain"
|
||||
// EventParameterLinkId The ID for an outbound link or file download
|
||||
EventParameterLinkId EventParameter = "link_id"
|
||||
// EventParameterLinkUrl The full URL for an outbound link or file download
|
||||
EventParameterLinkUrl EventParameter = "link_url"
|
||||
// EventParameterOutbound Indicates whether a click was on an outbound link
|
||||
EventParameterOutbound EventParameter = "outbound"
|
||||
// EventParameterContentGroup The content group associated with a page or screen
|
||||
EventParameterContentGroup EventParameter = "content_group"
|
||||
// EventParameterContentId An ID for an article of content that a user interacted with
|
||||
EventParameterContentId EventParameter = "content_id"
|
||||
// EventParameterContentType The type of content that a user interacted with
|
||||
EventParameterContentType EventParameter = "content_type"
|
||||
// EventParameterPageLocation The complete URL of the webpage that someone visited on your website
|
||||
EventParameterPageLocation EventParameter = "page_location"
|
||||
// EventParameterPageReferrer The referring URL, which is the user's previous URL and can be your website's domain or other domains
|
||||
EventParameterPageReferrer EventParameter = "page_referrer"
|
||||
// EventParameterPageTitle The HTML page title that you set on your website
|
||||
EventParameterPageTitle EventParameter = "page_title"
|
||||
// EventParameterScreenResolution The resolution of a device, in the format (Width)x(Height)
|
||||
EventParameterScreenResolution EventParameter = "screen_resolution"
|
||||
// EventParameterAdFormat The format of an advertisement in an app
|
||||
EventParameterAdFormat EventParameter = "ad_format"
|
||||
// EventParameterAdPlatform The platform used to surface an advertisement in an app
|
||||
EventParameterAdPlatform EventParameter = "ad_platform"
|
||||
// EventParameterAdSource The source network that served an advertisement
|
||||
EventParameterAdSource EventParameter = "ad_source"
|
||||
// EventParameterAdUnitId The unique identifier for an ad unit
|
||||
EventParameterAdUnitId EventParameter = "ad_unit_id"
|
||||
// EventParameterAdUnitName The name you choose for an ad unit
|
||||
EventParameterAdUnitName EventParameter = "ad_unit_name"
|
||||
// EventParameterVideoProvider The source of an embedded video
|
||||
EventParameterVideoProvider EventParameter = "video_provider"
|
||||
// EventParameterVideoTitle The title of an embedded video
|
||||
EventParameterVideoTitle EventParameter = "video_title"
|
||||
// EventParameterVideoUrl The url of an embedded video
|
||||
EventParameterVideoUrl EventParameter = "video_url"
|
||||
|
||||
// Additional undocumented parameters
|
||||
|
||||
EventParameterMethod EventParameter = "method"
|
||||
EventParameterPaymentType EventParameter = "payment_type"
|
||||
EventParameterTransactionID EventParameter = "transaction_id"
|
||||
)
|
||||
|
||||
func (s EventParameter) String() string {
|
||||
return string(s)
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
package v2
|
||||
|
||||
// EventParameterNumber as number
|
||||
// See https://support.google.com/analytics/table/13594742?sjid=7861230991468479976-EU
|
||||
type EventParameterNumber string
|
||||
|
||||
const (
|
||||
EventParameterNumberValue EventParameterNumber = "value"
|
||||
// EventParameterNumberFirebaseError The error code reported by the Firebase SDK.
|
||||
EventParameterNumberFirebaseError EventParameterNumber = "firebase_error"
|
||||
// EventParameterNumberFreeTrial Signifies that an in-app purchase is a free trial
|
||||
EventParameterNumberFreeTrial EventParameterNumber = "free_trial"
|
||||
// EventParameterNumberMessageDeviceTime The Firebase Cloud Messaging or Firebase In-App Messaging delivery epoch timestamp in UTC. (None)
|
||||
EventParameterNumberMessageDeviceTime EventParameterNumber = "message_device_time"
|
||||
// EventParameterNumberMessageTime The Firebase Cloud Messaging message notification epoch timestamp in UTC. (None)
|
||||
EventParameterNumberMessageTime EventParameterNumber = "message_time"
|
||||
// EventParameterNumberShipping The shipping cost associated with a transaction
|
||||
EventParameterNumberShipping EventParameterNumber = "shipping"
|
||||
// EventParameterNumberTax The tax cost associated with a transaction
|
||||
EventParameterNumberTax EventParameterNumber = "tax"
|
||||
)
|
||||
|
||||
func (s EventParameterNumber) String() string {
|
||||
return string(s)
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
package v2
|
||||
|
||||
/*
|
||||
*
|
||||
|
||||
promotion_id: "pi",
|
||||
promotion_name: "pn",
|
||||
creative_name: "cn",
|
||||
creative_slot: "cs",
|
||||
*/
|
||||
type Item struct {
|
||||
// Example: 12345
|
||||
ID *string `json:"id,omitempty" mapstructure:"id,omitempty"`
|
||||
// Example: Stan and Friends Tee
|
||||
Name *string `json:"nm,omitempty" mapstructure:"nm,omitempty"`
|
||||
// Example: Google
|
||||
Brand *string `json:"br,omitempty" mapstructure:"br,omitempty"`
|
||||
// Example: men
|
||||
CategoryHierarchy1 *string `json:"ca,omitempty" mapstructure:"ca,omitempty"`
|
||||
// Example: t-shirts
|
||||
CategoryHierarchy2 *string `json:"c2,omitempty" mapstructure:"c2,omitempty"`
|
||||
// Example: men
|
||||
CategoryHierarchy3 *string `json:"c3,omitempty" mapstructure:"c3,omitempty"`
|
||||
// Example: men
|
||||
CategoryHierarchy4 *string `json:"c4,omitempty" mapstructure:"c4,omitempty"`
|
||||
// Example: men
|
||||
CategoryHierarchy5 *string `json:"c5,omitempty" mapstructure:"c5,omitempty"`
|
||||
// Example: Yellow
|
||||
Variant *string `json:"va,omitempty" mapstructure:"va,omitempty"`
|
||||
// Example: 123.45
|
||||
Price *string `json:"pr,omitempty" mapstructure:"pr,omitempty"`
|
||||
// Example: 1
|
||||
Quantity *string `json:"qt,omitempty" mapstructure:"qt,omitempty"`
|
||||
// Example: 50%OFF
|
||||
Coupon *string `json:"cp,omitempty" mapstructure:"cp,omitempty"`
|
||||
// Example: cross-selling: mens
|
||||
ListName *string `json:"ln,omitempty" mapstructure:"ln,omitempty"`
|
||||
// Example: 10
|
||||
ListPosition *string `json:"lp,omitempty" mapstructure:"lp,omitempty"`
|
||||
// Example: id-mens-123
|
||||
ListID *string `json:"li,omitempty" mapstructure:"li,omitempty"`
|
||||
// Example: 10.00
|
||||
Discount *string `json:"ds,omitempty" mapstructure:"ds,omitempty"`
|
||||
// Example: Foo Marketplace
|
||||
Affiliation *string `json:"af,omitempty" mapstructure:"af,omitempty"`
|
||||
// Example: ChIJIQBpAG2ahYAR_6128GcTUEo
|
||||
LocationID *string `json:"lo,omitempty" mapstructure:"lo,omitempty"`
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
package v2
|
||||
|
||||
type Marshler interface {
|
||||
MarshalMPv2() (*Event, error)
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
package v2
|
||||
|
||||
type SST struct {
|
||||
// Example: 1
|
||||
ADR *string `json:"adr,omitempty" mapstructure:"adr,omitempty"`
|
||||
// Example: 1---
|
||||
USPrivacy *string `json:"us_privacy,omitempty" mapstructure:"us_privacy,omitempty"`
|
||||
// Example: 542231386.1709295522
|
||||
RND *string `json:"rnd,omitempty" mapstructure:"rnd,omitempty"`
|
||||
// Example: google.de
|
||||
ETLD *string `json:"etld,omitempty" mapstructure:"etld,omitempty"`
|
||||
// Example: region1
|
||||
GCSub *string `json:"gcsub,omitempty" mapstructure:"gcsub,omitempty"`
|
||||
// Example: DE
|
||||
UC *string `json:"uc,omitempty" mapstructure:"uc,omitempty"`
|
||||
// Example: 1708250245344
|
||||
TFT *string `json:"tft,omitempty" mapstructure:"tft,omitempty"`
|
||||
// Example: 13l3l3l3l1
|
||||
GCD *string `json:"gcd,omitempty" mapstructure:"gcd,omitempty"`
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
package v2
|
||||
|
||||
type Unmarshler interface {
|
||||
UnmarshalMPv2(*Event) error
|
||||
}
|
||||
@ -1,51 +1,55 @@
|
||||
package v2
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
Client struct {
|
||||
GTag struct {
|
||||
l *zap.Logger
|
||||
url string
|
||||
path string
|
||||
host string
|
||||
cookies []string
|
||||
richsstsse string
|
||||
trackingID string
|
||||
measurementID string
|
||||
protocolVersion string
|
||||
httpClient *http.Client
|
||||
middlewares []ClientMiddleware
|
||||
middlewares []GTagMiddleware
|
||||
}
|
||||
ClientOption func(*Client)
|
||||
ClientHandler func(r *http.Request, event *Event) error
|
||||
ClientMiddleware func(next ClientHandler) ClientHandler
|
||||
GTagOption func(*GTag)
|
||||
GTagHandler func(r *http.Request, payload *gtag.Payload) error
|
||||
GTagMiddleware func(next GTagHandler) GTagHandler
|
||||
)
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Options
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func ClientWithHTTPClient(v *http.Client) ClientOption {
|
||||
return func(o *Client) {
|
||||
func GTagWithHTTPClient(v *http.Client) GTagOption {
|
||||
return func(o *GTag) {
|
||||
o.httpClient = v
|
||||
}
|
||||
}
|
||||
|
||||
func ClientWithCookies(v ...string) ClientOption {
|
||||
return func(o *Client) {
|
||||
func GTagWithPath(v string) GTagOption {
|
||||
return func(o *GTag) {
|
||||
o.path = v
|
||||
}
|
||||
}
|
||||
|
||||
func GTagWithCookies(v ...string) GTagOption {
|
||||
return func(o *GTag) {
|
||||
o.cookies = append(o.cookies, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func ClientWithMiddlewares(v ...ClientMiddleware) ClientOption {
|
||||
return func(o *Client) {
|
||||
func GTagWithMiddlewares(v ...GTagMiddleware) GTagOption {
|
||||
return func(o *GTag) {
|
||||
o.middlewares = append(o.middlewares, v...)
|
||||
}
|
||||
}
|
||||
@ -54,10 +58,11 @@ func ClientWithMiddlewares(v ...ClientMiddleware) ClientOption {
|
||||
// ~ Constructor
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func NewClient(l *zap.Logger, url, trackingID string, opts ...ClientOption) *Client {
|
||||
inst := &Client{
|
||||
func NewGTag(l *zap.Logger, host, trackingID string, opts ...GTagOption) *GTag {
|
||||
inst := >ag{
|
||||
l: l,
|
||||
url: url,
|
||||
host: host,
|
||||
path: "/g/collect",
|
||||
cookies: []string{"gtm_auth", "gtm_debug", "gtm_preview"},
|
||||
trackingID: trackingID,
|
||||
protocolVersion: "2",
|
||||
@ -66,6 +71,14 @@ func NewClient(l *zap.Logger, url, trackingID string, opts ...ClientOption) *Cli
|
||||
for _, opt := range opts {
|
||||
opt(inst)
|
||||
}
|
||||
inst.middlewares = append(inst.middlewares,
|
||||
GTagMiddlewareRichsstsse,
|
||||
GTagMiddlewareTrackingID(inst.trackingID),
|
||||
GTagMiddlewarProtocolVersion("2"),
|
||||
GTagMiddlewarIsDebug,
|
||||
GTagMiddlewarClientID,
|
||||
GTagMiddlewarSessionID(inst.trackingID),
|
||||
)
|
||||
return inst
|
||||
}
|
||||
|
||||
@ -73,7 +86,7 @@ func NewClient(l *zap.Logger, url, trackingID string, opts ...ClientOption) *Cli
|
||||
// ~ Getter
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func (c *Client) HTTPClient() *http.Client {
|
||||
func (c *GTag) HTTPClient() *http.Client {
|
||||
return c.httpClient
|
||||
}
|
||||
|
||||
@ -81,74 +94,45 @@ func (c *Client) HTTPClient() *http.Client {
|
||||
// ~ Public methods
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func (c *Client) Send(r *http.Request, event *Event) error {
|
||||
yes := "1"
|
||||
|
||||
// set default values
|
||||
event.TrackingID = &c.trackingID
|
||||
event.Richsstsse = &c.richsstsse
|
||||
event.ProtocolVersion = &c.protocolVersion
|
||||
|
||||
event.IgnoreReferrer = &yes
|
||||
|
||||
{ // set referrer parameter
|
||||
if referrer, err := url.Parse(r.Referer()); err != nil {
|
||||
c.l.With(zap.Error(err)).Warn("failed to parse referrer")
|
||||
} else {
|
||||
event.DocumentLocation = &referrer.Path
|
||||
event.DocumentHostname = &referrer.Host
|
||||
}
|
||||
}
|
||||
|
||||
{ // TODO check
|
||||
if value, _ := r.Cookie("gtm_debug"); value != nil {
|
||||
event.IsDebug = &yes
|
||||
}
|
||||
}
|
||||
|
||||
{ // set client id
|
||||
if value, _ := r.Cookie("_ga"); value != nil {
|
||||
clientID := strings.TrimPrefix(value.Value, "GA1.1.")
|
||||
event.ClientID = &clientID
|
||||
}
|
||||
}
|
||||
|
||||
{ // pass through cookies
|
||||
for _, value := range c.cookies {
|
||||
if cookie, _ := r.Cookie(value); cookie != nil {
|
||||
r.Header.Add("Cookie", cookie.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *GTag) Send(r *http.Request, payload *gtag.Payload) error {
|
||||
next := c.SendRaw
|
||||
for _, middleware := range c.middlewares {
|
||||
next = middleware(next)
|
||||
}
|
||||
|
||||
return next(r, event)
|
||||
return next(r, payload)
|
||||
}
|
||||
|
||||
func (c *Client) SendRaw(r *http.Request, event *Event) error {
|
||||
values, body, err := Encode(event)
|
||||
func (c *GTag) SendRaw(r *http.Request, payload *gtag.Payload) error {
|
||||
values, body, err := gtag.Encode(payload)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshall event")
|
||||
return errors.Wrap(err, "failed to encode payload")
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(
|
||||
r.Context(),
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("%s?%s", c.url, EncodeValues(values)),
|
||||
fmt.Sprintf("%s%s?%s", c.host, c.path, gtag.EncodeValues(values)),
|
||||
body,
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create request")
|
||||
}
|
||||
|
||||
// TODO valiate: copy headers
|
||||
req.Header = r.Header.Clone()
|
||||
|
||||
// forward cookies
|
||||
for _, cookie := range c.cookies {
|
||||
if value, _ := r.Cookie(cookie); value != nil {
|
||||
req.AddCookie(value)
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to send request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
var body string
|
||||
37
pkg/client/gtag_test.go
Normal file
37
pkg/client/gtag_test.go
Normal file
@ -0,0 +1,37 @@
|
||||
package client_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/http/httputil"
|
||||
"testing"
|
||||
|
||||
"github.com/foomo/sesamy-go/pkg/client"
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
|
||||
"github.com/foomo/sesamy-go/pkg/sesamy"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap/zaptest"
|
||||
)
|
||||
|
||||
func TestNewGtag(t *testing.T) {
|
||||
t.Parallel()
|
||||
l := zaptest.NewLogger(t)
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Helper()
|
||||
out, err := httputil.DumpRequest(r, true)
|
||||
if assert.NoError(t, err) {
|
||||
t.Log(string(out))
|
||||
}
|
||||
}))
|
||||
|
||||
c := client.NewGTag(l, s.URL, "GA-XXXXXX")
|
||||
incomingReq, err := http.NewRequestWithContext(t.Context(), http.MethodGet, "/foo/bar", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = c.Send(incomingReq, >ag.Payload{
|
||||
EventName: gtag.Set(sesamy.EventName("page_view")),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
82
pkg/client/gtagmiddleware.go
Normal file
82
pkg/client/gtagmiddleware.go
Normal file
@ -0,0 +1,82 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
|
||||
"github.com/foomo/sesamy-go/pkg/session"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func GTagMiddlewareRichsstsse(next GTagHandler) GTagHandler {
|
||||
v := ""
|
||||
return func(r *http.Request, payload *gtag.Payload) error {
|
||||
payload.Richsstsse = &v
|
||||
return next(r, payload)
|
||||
}
|
||||
}
|
||||
|
||||
func GTagMiddlewareTrackingID(v string) GTagMiddleware {
|
||||
return func(next GTagHandler) GTagHandler {
|
||||
return func(r *http.Request, payload *gtag.Payload) error {
|
||||
payload.TrackingID = &v
|
||||
return next(r, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GTagMiddlewarProtocolVersion(v string) GTagMiddleware {
|
||||
return func(next GTagHandler) GTagHandler {
|
||||
return func(r *http.Request, payload *gtag.Payload) error {
|
||||
payload.ProtocolVersion = &v
|
||||
return next(r, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GTagMiddlewarIsDebug(next GTagHandler) GTagHandler {
|
||||
v := "1"
|
||||
return func(r *http.Request, payload *gtag.Payload) error {
|
||||
if session.IsGTMDebug(r) {
|
||||
payload.IsDebug = &v
|
||||
}
|
||||
return next(r, payload)
|
||||
}
|
||||
}
|
||||
|
||||
func GTagMiddlewarClientID(next GTagHandler) GTagHandler {
|
||||
return func(r *http.Request, payload *gtag.Payload) error {
|
||||
value, err := session.ParseGAClientID(r)
|
||||
if err != nil && !errors.Is(err, http.ErrNoCookie) {
|
||||
return errors.Wrap(err, "failed to parse client cookie")
|
||||
}
|
||||
if value != "" {
|
||||
payload.ClientID = &value
|
||||
}
|
||||
return next(r, payload)
|
||||
}
|
||||
}
|
||||
|
||||
func GTagMiddlewarWithoutCancel(next GTagHandler) GTagHandler {
|
||||
return func(r *http.Request, payload *gtag.Payload) error {
|
||||
return next(r.WithContext(context.WithoutCancel(r.Context())), payload)
|
||||
}
|
||||
}
|
||||
|
||||
func GTagMiddlewarSessionID(measurementID string) GTagMiddleware {
|
||||
measurementID = strings.Split(measurementID, "-")[1]
|
||||
return func(next GTagHandler) GTagHandler {
|
||||
return func(r *http.Request, payload *gtag.Payload) error {
|
||||
value, err := session.ParseGASessionID(r, measurementID)
|
||||
if err != nil && !errors.Is(err, http.ErrNoCookie) {
|
||||
return errors.Wrap(err, "failed to parse session cookie")
|
||||
}
|
||||
if value != "" {
|
||||
payload.SessionID = &value
|
||||
}
|
||||
return next(r, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
181
pkg/client/mpv2.go
Normal file
181
pkg/client/mpv2.go
Normal file
@ -0,0 +1,181 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
|
||||
"github.com/foomo/sesamy-go/pkg/sesamy"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
MPv2 struct {
|
||||
l *zap.Logger
|
||||
path string
|
||||
host string
|
||||
cookies []string
|
||||
// To create a new secret, navigate in the Google Analytics UI to:
|
||||
// Admin > Data Streams > choose your stream > Measurement Protocol > Create
|
||||
apiSecret string
|
||||
// Measurement ID. The identifier for a Data Stream. Found in the Google Analytics UI under:
|
||||
// Admin > Data Streams > choose your stream > Measurement ID
|
||||
measurementID string
|
||||
protocolVersion string
|
||||
httpClient *http.Client
|
||||
middlewares []MPv2Middleware
|
||||
}
|
||||
MPv2Option func(*MPv2)
|
||||
MPv2Handler func(r *http.Request, payload *mpv2.Payload[any]) error
|
||||
MPv2Middleware func(next MPv2Handler) MPv2Handler
|
||||
)
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Options
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func MPv2WithHTTPClient(v *http.Client) MPv2Option {
|
||||
return func(o *MPv2) {
|
||||
o.httpClient = v
|
||||
}
|
||||
}
|
||||
|
||||
func MPv2WithPath(v string) MPv2Option {
|
||||
return func(o *MPv2) {
|
||||
o.path = v
|
||||
}
|
||||
}
|
||||
|
||||
func MPv2WithCookies(v ...string) MPv2Option {
|
||||
return func(o *MPv2) {
|
||||
o.cookies = append(o.cookies, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func MPv2WithAPISecret(v string) MPv2Option {
|
||||
return func(o *MPv2) {
|
||||
o.apiSecret = v
|
||||
}
|
||||
}
|
||||
|
||||
func MPv2WithMeasurementID(v string) MPv2Option {
|
||||
return func(o *MPv2) {
|
||||
o.measurementID = v
|
||||
}
|
||||
}
|
||||
|
||||
func MPv2WithMiddlewares(v ...MPv2Middleware) MPv2Option {
|
||||
return func(o *MPv2) {
|
||||
o.middlewares = append(o.middlewares, v...)
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Constructor
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func NewMPv2(l *zap.Logger, host string, opts ...MPv2Option) *MPv2 {
|
||||
inst := &MPv2{
|
||||
l: l,
|
||||
host: host,
|
||||
path: "/mp/collect",
|
||||
cookies: []string{"gtm_auth", "gtm_debug", "gtm_preview"},
|
||||
protocolVersion: "2",
|
||||
httpClient: http.DefaultClient,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(inst)
|
||||
}
|
||||
inst.middlewares = append(inst.middlewares,
|
||||
MPv2MiddlewarClientID,
|
||||
)
|
||||
return inst
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Getter
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func (c *MPv2) HTTPClient() *http.Client {
|
||||
return c.httpClient
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Public methods
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func (c *MPv2) Collect(r *http.Request, events ...sesamy.AnyEvent) error {
|
||||
payload := mpv2.NewPayload[any]()
|
||||
for _, event := range events {
|
||||
payload.Events = append(payload.Events, event.AnyEvent())
|
||||
}
|
||||
return c.SendPayload(r, payload)
|
||||
}
|
||||
|
||||
func (c *MPv2) SendPayload(r *http.Request, payload *mpv2.Payload[any]) error {
|
||||
next := c.SendRaw
|
||||
for _, middleware := range c.middlewares {
|
||||
next = middleware(next)
|
||||
}
|
||||
return next(r, payload)
|
||||
}
|
||||
|
||||
func (c *MPv2) SendRaw(r *http.Request, payload *mpv2.Payload[any]) error {
|
||||
jsonPayload, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to encode payload")
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(
|
||||
r.Context(),
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("%s%s", c.host, c.path),
|
||||
bytes.NewReader(jsonPayload),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create request")
|
||||
}
|
||||
|
||||
// query
|
||||
qry := req.URL.Query()
|
||||
if len(c.apiSecret) > 0 {
|
||||
qry.Add("api_secret", c.apiSecret)
|
||||
}
|
||||
if len(c.measurementID) > 0 {
|
||||
qry.Add("measurement_id", c.measurementID)
|
||||
}
|
||||
req.URL.RawQuery = qry.Encode()
|
||||
|
||||
// TODO valiate: copy headers
|
||||
req.Header = r.Header.Clone()
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// forward cookies
|
||||
for _, cookie := range c.cookies {
|
||||
if value, _ := r.Cookie(cookie); value != nil {
|
||||
req.AddCookie(value)
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to send request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
var body string
|
||||
if out, err := io.ReadAll(resp.Body); err != nil {
|
||||
c.l.With(zap.Error(err)).Warn(err.Error())
|
||||
} else {
|
||||
body = string(out)
|
||||
}
|
||||
return errors.Errorf("unexpected response status: %d (%s)", resp.StatusCode, body)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
38
pkg/client/mpv2_test.go
Normal file
38
pkg/client/mpv2_test.go
Normal file
@ -0,0 +1,38 @@
|
||||
package client_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/http/httputil"
|
||||
"testing"
|
||||
|
||||
"github.com/foomo/sesamy-go/pkg/client"
|
||||
"github.com/foomo/sesamy-go/pkg/event"
|
||||
"github.com/foomo/sesamy-go/pkg/event/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap/zaptest"
|
||||
)
|
||||
|
||||
func TestNewMPv2(t *testing.T) {
|
||||
t.Parallel()
|
||||
l := zaptest.NewLogger(t)
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Helper()
|
||||
out, err := httputil.DumpRequest(r, true)
|
||||
if assert.NoError(t, err) {
|
||||
t.Log(string(out))
|
||||
}
|
||||
}))
|
||||
|
||||
c := client.NewMPv2(l, s.URL)
|
||||
incomingReq, err := http.NewRequestWithContext(t.Context(), http.MethodGet, "/foo/bar", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = c.Collect(incomingReq, event.NewPageView(params.PageView{
|
||||
PageTitle: "foo",
|
||||
PageLocation: "bar",
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
70
pkg/client/mpv2middleware.go
Normal file
70
pkg/client/mpv2middleware.go
Normal file
@ -0,0 +1,70 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
|
||||
"github.com/foomo/sesamy-go/pkg/session"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func MPv2MiddlewarSessionID(measurementID string) MPv2Middleware {
|
||||
measurementID = strings.Split(measurementID, "-")[1]
|
||||
return func(next MPv2Handler) MPv2Handler {
|
||||
return func(r *http.Request, payload *mpv2.Payload[any]) error {
|
||||
if payload.SessionID == "" {
|
||||
value, err := session.ParseGASessionID(r, measurementID)
|
||||
if err != nil && !errors.Is(err, http.ErrNoCookie) {
|
||||
return errors.Wrap(err, "failed to parse client cookie")
|
||||
}
|
||||
payload.SessionID = value
|
||||
}
|
||||
return next(r, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func MPv2MiddlewarClientID(next MPv2Handler) MPv2Handler {
|
||||
return func(r *http.Request, payload *mpv2.Payload[any]) error {
|
||||
if payload.ClientID == "" {
|
||||
value, err := session.ParseGAClientID(r)
|
||||
if err != nil && !errors.Is(err, http.ErrNoCookie) {
|
||||
return errors.Wrap(err, "failed to parse client cookie")
|
||||
}
|
||||
payload.ClientID = value
|
||||
}
|
||||
return next(r, payload)
|
||||
}
|
||||
}
|
||||
|
||||
func MPv2MiddlewarDebugMode(next MPv2Handler) MPv2Handler {
|
||||
return func(r *http.Request, payload *mpv2.Payload[any]) error {
|
||||
if !payload.DebugMode {
|
||||
payload.DebugMode = session.IsGTMDebug(r)
|
||||
}
|
||||
return next(r, payload)
|
||||
}
|
||||
}
|
||||
|
||||
func MPv2MiddlewarWithoutCancel(next MPv2Handler) MPv2Handler {
|
||||
return func(r *http.Request, payload *mpv2.Payload[any]) error {
|
||||
return next(r.WithContext(context.WithoutCancel(r.Context())), payload)
|
||||
}
|
||||
}
|
||||
|
||||
func MPv2MiddlewareUserID(cookieName string) MPv2Middleware {
|
||||
return func(next MPv2Handler) MPv2Handler {
|
||||
return func(r *http.Request, payload *mpv2.Payload[any]) error {
|
||||
if payload.UserID == "" {
|
||||
value, err := r.Cookie(cookieName)
|
||||
if err != nil && !errors.Is(err, http.ErrNoCookie) {
|
||||
return err
|
||||
}
|
||||
payload.UserID = value.Value
|
||||
}
|
||||
return next(r, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
196
pkg/collect/collect.go
Normal file
196
pkg/collect/collect.go
Normal file
@ -0,0 +1,196 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
|
||||
gtaghttp "github.com/foomo/sesamy-go/pkg/http/gtag"
|
||||
mpv2http "github.com/foomo/sesamy-go/pkg/http/mpv2"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
Collect struct {
|
||||
l *zap.Logger
|
||||
taggingURL string
|
||||
taggingClient *http.Client
|
||||
gtagHTTPMiddlewares []gtaghttp.Middleware
|
||||
mpv2HTTPMiddlewares []mpv2http.Middleware
|
||||
}
|
||||
Option func(*Collect) error
|
||||
)
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Options
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func WithTagging(v string) Option {
|
||||
return func(c *Collect) error {
|
||||
c.taggingURL = v
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithTaggingClient(v *http.Client) Option {
|
||||
return func(c *Collect) error {
|
||||
c.taggingClient = v
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithGTagHTTPMiddlewares(v ...gtaghttp.Middleware) Option {
|
||||
return func(c *Collect) error {
|
||||
c.gtagHTTPMiddlewares = append(c.gtagHTTPMiddlewares, v...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithMPv2HTTPMiddlewares(v ...mpv2http.Middleware) Option {
|
||||
return func(c *Collect) error {
|
||||
c.mpv2HTTPMiddlewares = append(c.mpv2HTTPMiddlewares, v...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Constructor
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func New(l *zap.Logger, opts ...Option) (*Collect, error) {
|
||||
inst := &Collect{
|
||||
l: l,
|
||||
taggingClient: http.DefaultClient,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
if err := opt(inst); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inst, nil
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Public methods
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func (c *Collect) GTagHTTPHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// retrieve payload
|
||||
payload := gtaghttp.Handler(w, r)
|
||||
if payload == nil {
|
||||
http.Error(w, "failed to decode payload", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// compose middlewares
|
||||
next := c.gtagHandler
|
||||
for _, middleware := range c.gtagHTTPMiddlewares {
|
||||
next = middleware(next)
|
||||
}
|
||||
|
||||
// run handler
|
||||
if err := next(c.l, w, r, payload); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Collect) MPv2HTTPHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// retrieve payload
|
||||
payload := mpv2http.Handler(w, r)
|
||||
|
||||
// compose middlewares
|
||||
next := c.mpv2Handler
|
||||
for _, middleware := range c.mpv2HTTPMiddlewares {
|
||||
next = middleware(next)
|
||||
}
|
||||
|
||||
// run handler
|
||||
if err := next(c.l, w, r, payload); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Private methods
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func (c *Collect) gtagHandler(l *zap.Logger, w http.ResponseWriter, r *http.Request, payload *gtag.Payload) error {
|
||||
values, body, err := gtag.Encode(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(r.Context(), http.MethodPost, fmt.Sprintf("%s%s?%s", c.taggingURL, "/g/collect", gtag.EncodeValues(values)), body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create request")
|
||||
}
|
||||
|
||||
// copy headers
|
||||
req.Header = r.Header.Clone()
|
||||
|
||||
resp, err := c.taggingClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// copy headers
|
||||
for key, values := range resp.Header {
|
||||
for _, value := range values {
|
||||
w.Header().Add(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := io.Copy(w, resp.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collect) mpv2Handler(l *zap.Logger, w http.ResponseWriter, r *http.Request, payload *mpv2.Payload[any]) error {
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(r.Context(), http.MethodPost, fmt.Sprintf("%s%s", c.taggingURL, "/mp/collect"), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create request")
|
||||
}
|
||||
|
||||
// copy headers
|
||||
req.Header = r.Header.Clone()
|
||||
// copy raw query
|
||||
req.URL.RawQuery = r.URL.RawQuery
|
||||
|
||||
resp, err := c.taggingClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// copy headers
|
||||
for key, values := range resp.Header {
|
||||
for _, value := range values {
|
||||
w.Header().Add(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := io.Copy(w, resp.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
154
pkg/collect/collect_test.go
Normal file
154
pkg/collect/collect_test.go
Normal file
@ -0,0 +1,154 @@
|
||||
package collect_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/foomo/sesamy-go/pkg/collect"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap/zaptest"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
opts []collect.Option
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "create with default options",
|
||||
opts: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "create with custom tagging URL",
|
||||
opts: []collect.Option{
|
||||
collect.WithTagging("https://example.com"),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "create with custom HTTP client",
|
||||
opts: []collect.Option{
|
||||
collect.WithTaggingClient(http.DefaultClient),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
l := zaptest.NewLogger(t)
|
||||
c, err := collect.New(l, tt.opts...)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, c)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollect_GTagHTTPHandler(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
setupMockServer func(query string) *httptest.Server
|
||||
expectedStatus int
|
||||
}{
|
||||
{
|
||||
name: "successful request",
|
||||
query: "v=2&tid=G-C5FH0JEWES>m=45he5641h1v9208481823z89208852519za204zb9208852519&_p=1749197326339&_dbg=1&gcs=G111&gcd=13t3t3t2t5l1&npa=0&dma_cps=syphamo&dma=1&tag_exp=101509157~103116026~103200004~103233427~103351869~103351871~104653070~104653072~104661466~104661468~104698127~104698129&gdid=dMWZhNz&cid=1897509477.1748871081&ecid=1224659014&ul=en-us&sr=1728x1117&_fplc=0&ur=DE&uaa=arm&uab=64&uafvl=Chromium%3B136.0.7103.114%7CGoogle%2520Chrome%3B136.0.7103.114%7CNot.A%252FBrand%3B99.0.0.0&uamb=0&uam=&uap=macOS&uapv=15.5.0&uaw=0&are=1&frm=0&pscdl=noapi&_eu=AAAAAAQ&sst.rnd=602677853.1749197331&sst.etld=google.de&sst.gcsub=region1&sst.adr=1&sst.us_privacy=1YNY&sst.tft=1749197326339&sst.lpc=223090308&sst.navt=r&sst.ude=0&sst.sw_exp=1&_s=1&sid=1749197325&sct=4&seg=1&dl=https%3A%2F%2Fnode-b.stage.geschenkidee.ch%2Fsets-geschenkkoerbe&dr=https%3A%2F%2Fnode-b.stage.geschenkidee.ch%2Fgeschenkgutscheine-2&dt=Geschenksets%202025%20%E2%80%93%20Kreative%20Sets%20f%C3%BCr%20jeden%20Anlass%20%7C%20geschenkidee.ch&_tu=DA&en=page_view&epn.emarsys_page_view_id=1550965197&tfd=4880&richsstsse",
|
||||
setupMockServer: func(query string) *httptest.Server {
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/g/collect", r.URL.Path)
|
||||
if !assert.Len(t, r.URL.RawQuery, len(query)) {
|
||||
t.Logf("expected: %s", query)
|
||||
t.Logf("actual: %s", r.URL.RawQuery)
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
},
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "server error",
|
||||
query: "",
|
||||
setupMockServer: func(query string) *httptest.Server {
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}))
|
||||
},
|
||||
expectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
l := zaptest.NewLogger(t)
|
||||
|
||||
// Setup mock server
|
||||
mockServer := tt.setupMockServer(tt.query)
|
||||
defer mockServer.Close()
|
||||
|
||||
// Create collector with mock server URL
|
||||
c, err := collect.New(l, collect.WithTagging(mockServer.URL))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create test request
|
||||
req := httptest.NewRequest(http.MethodPost, "/collect?"+tt.query, nil)
|
||||
req.Header.Set("User-Agent", "test-agent")
|
||||
|
||||
// Create response recorder
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
// Call the handler
|
||||
c.GTagHTTPHandler(w, req)
|
||||
|
||||
// Assert response
|
||||
assert.Equal(t, tt.expectedStatus, w.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// func TestCollect_WithMiddleware(t *testing.T) {
|
||||
// logger := zap.NewNop()
|
||||
//
|
||||
// // Create a test middleware
|
||||
// testMiddleware := func(next gtaghttp.HandlerFunc) gtaghttp.HandlerFunc {
|
||||
// return func(l *zap.Logger, w http.ResponseWriter, r *http.Request, payload *gtag.Payload) error {
|
||||
// // Modify the payload or do something before passing to next handler
|
||||
// payload.ClientID = "test-client"
|
||||
// return next(l, w, r, payload)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// assert.Contains(t, r.URL.String(), "cid=test-client")
|
||||
// w.WriteHeader(http.StatusOK)
|
||||
// }))
|
||||
// defer mockServer.Close()
|
||||
//
|
||||
// c, err := New(logger,
|
||||
// WithTagging(mockServer.URL),
|
||||
// WithGTagHTTPMiddlewares(testMiddleware),
|
||||
// )
|
||||
// require.NoError(t, err)
|
||||
//
|
||||
// // Create test request
|
||||
// req := httptest.NewRequest(http.MethodPost, "/collect", nil)
|
||||
// w := httptest.NewRecorder()
|
||||
//
|
||||
// // Call the handler
|
||||
// c.GTagHTTPHandler(w, req)
|
||||
//
|
||||
// assert.Equal(t, http.StatusOK, w.Code)
|
||||
// }
|
||||
28
pkg/encoding/gtag/campaign.go
Normal file
28
pkg/encoding/gtag/campaign.go
Normal file
@ -0,0 +1,28 @@
|
||||
package gtag
|
||||
|
||||
type Campaign struct {
|
||||
// Campaign Medium ( utm_medium ), this will override the current values read from the url
|
||||
// Example: cpc
|
||||
CampaignMedium *string `json:"campaign_medium,omitempty" gtag:"cm,omitempty"`
|
||||
// Campaign Source ( utm_source ), this will override the current values read from the url
|
||||
// Example: google
|
||||
CampaignSource *string `json:"campaign_source,omitempty" gtag:"cs,omitempty"`
|
||||
// Campaign Name ( utm_campaign ), this will override the current values read from the url
|
||||
// Example: cpc
|
||||
CampaignName *string `json:"campaign_name,omitempty" gtag:"cn,omitempty"`
|
||||
// Campaign Content ( utm_content ), this will override the current values read from the url
|
||||
// Example: big banner
|
||||
CampaignContent *string `json:"campaign_content,omitempty" gtag:"cc,omitempty"`
|
||||
// Campaign Term ( utm_term ), this will override the current values read from the url
|
||||
// Example: summer
|
||||
CampaignTerm *string `json:"campaign_term,omitempty" gtag:"ck,omitempty"`
|
||||
// Campaign Creative Format ( utm_creative_format ), this will override the current values read from the url
|
||||
// Example: native
|
||||
CampaignCreativeFormat *string `json:"campaign_creative_format,omitempty" gtag:"ccf,omitempty"`
|
||||
// Campaign Marketing Tactic ( utm_marketing_tactic ), this will override the current values read from the url
|
||||
// Example: prospecting
|
||||
CampaignMarketingTactic *string `json:"campaign_marketing_tactic,omitempty" gtag:"cmt,omitempty"`
|
||||
// Random Number used to Dedupe gclid
|
||||
// Example: 342342343
|
||||
// GclidDeduper *string `json:"gclid_deduper,omitempty" gtag:"_rnd,omitempty"`
|
||||
}
|
||||
41
pkg/encoding/gtag/clienthints.go
Normal file
41
pkg/encoding/gtag/clienthints.go
Normal file
@ -0,0 +1,41 @@
|
||||
package gtag
|
||||
|
||||
type ClientHints struct {
|
||||
// Is a random hash generated on the page load.
|
||||
// Examole: 456193680
|
||||
// RandomPageLoadHash *string `json:"random_page_load_hash,omitempty" gtag:"_p,omitempty"`
|
||||
// Browser screen resolution in format width x height
|
||||
// Example: 2560x1440
|
||||
ScreenResolution *string `json:"screen_resolution,omitempty" gtag:"sr,omitempty"`
|
||||
// Browser active locale.
|
||||
// Example: es-es
|
||||
UserLanguage *string `json:"user_language,omitempty" gtag:"ul,omitempty"`
|
||||
// Example: x86
|
||||
UserAgentArchitecture *string `json:"user_agent_architecture,omitempty" gtag:"uaa,omitempty"`
|
||||
// The "bitness" of the user-agent's underlying CPU architecture. This is the size in bits of an integer or memory address—typically 64 or 32 bits.
|
||||
// Example: 32 | 64
|
||||
UserAgentBitness *string `json:"user_agent_bitness,omitempty" gtag:"uab,omitempty"`
|
||||
// The brand and full version information for each brand associated with the browser, in a comma-separated list
|
||||
// Example: Google Chrome;105.0.5195.127|Not)A;Brand;8.0.0.0|Chromium;105.0.5195.127
|
||||
UserAgentFullVersionList *string `json:"user_agent_full_version_list,omitempty" gtag:"uafvl,omitempty"`
|
||||
// Indicates whether the browser is on a mobile device
|
||||
// Example: 1
|
||||
UserAgentMobile *string `json:"user_agent_mobile,omitempty" gtag:"uamb,omitempty"`
|
||||
// The device model on which the browser is running. Will likely be empty for desktop browsers
|
||||
// Example: Nexus 6
|
||||
UserAgentModel *string `json:"user_agent_model,omitempty" gtag:"uam,omitempty"`
|
||||
// The platform or operating system on which the user agent is running
|
||||
// Example: Chromium OS | macOS | Android | iOS
|
||||
UserAgentPlatform *string `json:"user_agent_platform,omitempty" gtag:"uap,omitempty"`
|
||||
// The version of the operating system on which the user agent is running
|
||||
// Example: 14.0.0
|
||||
UserAgentPlatformVersion *string `json:"user_agent_platform_version,omitempty" gtag:"uapv,omitempty"`
|
||||
// Whatever Windows On Windows 64 Bit is supported. Used by "WoW64-ness" sites. ( running 32bits app on 64bits windows)
|
||||
// Example: 1
|
||||
UserAgentWOW64 *string `json:"user_agent_wow_64,omitempty" gtag:"uaw,omitempty"`
|
||||
// Added to report the current country for the user under some circumstanced. To be documented.
|
||||
// Example: ES
|
||||
UserCountry *string `json:"user_country,omitempty" gtag:"_uc,omitempty"`
|
||||
// Example: DE-BY
|
||||
UserRegion *string `json:"user_region,omitempty" gtag:"ur,omitempty"`
|
||||
}
|
||||
85
pkg/encoding/gtag/consent.go
Normal file
85
pkg/encoding/gtag/consent.go
Normal file
@ -0,0 +1,85 @@
|
||||
package gtag
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// See https://developers.google.com/tag-platform/security/concepts/consent-mode
|
||||
type Consent struct {
|
||||
// Current Google Consent Status. Format 'G1'+'AdsStorageBoolStatus'`+'AnalyticsStorageBoolStatus'
|
||||
// Example: G101
|
||||
GoogleConsentStatus *string `json:"google_consent_status,omitempty" gtag:"gcs,omitempty"`
|
||||
// Will be added with the value "1" if the Google Consent has just been updated (wait_for_update setting on GTAG)
|
||||
// Example: 1
|
||||
GoogleConsentUpdate *string `json:"google_consent_update,omitempty" gtag:"gcu,omitempty"`
|
||||
// Documented values, 1 or 2, no more info on the meaning
|
||||
// Example: 2
|
||||
GoogleConsentUpdateType *string `json:"google_consent_update_type,omitempty" gtag:"gcut,omitempty"`
|
||||
// Will be added with the value "1" if the Google Consent had a default value before getting an update
|
||||
// Example: G111
|
||||
GoogleConsentDefault *string `json:"google_consent_default,omitempty" gtag:"gcd,omitempty"`
|
||||
// Example: 1
|
||||
// DigitalMarketAct *string `json:"digital_market_act,omitempty" gtag:"dma,omitempty"`
|
||||
// Example: sypham
|
||||
// DigitalMarketActParameters *string `json:"digital_market_act_parameters,omitempty" gtag:"dma_cps,omitempty"`
|
||||
// Example: noapi | denied
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Public methods
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func (c Consent) AdStorage() bool {
|
||||
if c.GoogleConsentDefault != nil {
|
||||
gcd := strings.Split(*c.GoogleConsentDefault, "")
|
||||
if len(gcd) > 3 {
|
||||
return slices.Contains([]string{"l", "t", "r", "n", "u", "v"}, gcd[2])
|
||||
}
|
||||
}
|
||||
if c.GoogleConsentUpdate != nil {
|
||||
gcs := *c.GoogleConsentUpdate
|
||||
if strings.HasPrefix(gcs, "G1") && len(gcs) == 4 {
|
||||
return gcs[2:3] == "1"
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (c Consent) AnalyticsStorage() bool {
|
||||
if c.GoogleConsentDefault != nil {
|
||||
gcd := strings.Split(*c.GoogleConsentDefault, "")
|
||||
if len(gcd) > 5 {
|
||||
return slices.Contains([]string{"l", "t", "r", "n", "u", "v"}, gcd[4])
|
||||
}
|
||||
}
|
||||
if c.GoogleConsentUpdate != nil {
|
||||
gcs := *c.GoogleConsentUpdate
|
||||
if strings.HasPrefix(gcs, "G1") && len(gcs) == 4 {
|
||||
return gcs[3:4] == "1"
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (c Consent) AdUserData() bool {
|
||||
if c.GoogleConsentDefault != nil {
|
||||
gcd := strings.Split(*c.GoogleConsentDefault, "")
|
||||
if len(gcd) > 7 {
|
||||
return slices.Contains([]string{"l", "t", "r", "n", "u", "v"}, gcd[6])
|
||||
}
|
||||
}
|
||||
return c.AdStorage()
|
||||
}
|
||||
|
||||
func (c Consent) AdPersonalization() bool {
|
||||
if c.GoogleConsentDefault != nil {
|
||||
gcd := strings.Split(*c.GoogleConsentDefault, "")
|
||||
if len(gcd) > 9 {
|
||||
return slices.Contains([]string{"l", "t", "r", "n", "u", "v"}, gcd[8])
|
||||
}
|
||||
}
|
||||
return c.AdStorage()
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package v2
|
||||
package gtag
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
@ -1,3 +1,3 @@
|
||||
package v2
|
||||
package gtag
|
||||
|
||||
type Data map[string]any
|
||||
124
pkg/encoding/gtag/decode.go
Normal file
124
pkg/encoding/gtag/decode.go
Normal file
@ -0,0 +1,124 @@
|
||||
package gtag
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func DecodeRequest(r http.Request, target any) error {
|
||||
u, err := url.ParseRequestURI(r.RequestURI)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse request URI")
|
||||
}
|
||||
return Decode(u.Query(), target)
|
||||
}
|
||||
|
||||
func DecodeQuery(input string, tarteg any) error {
|
||||
values, err := url.ParseQuery(input)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse query")
|
||||
}
|
||||
return Decode(values, tarteg)
|
||||
}
|
||||
|
||||
// Decode an incoming request into an Payload
|
||||
func Decode(values url.Values, target any) error {
|
||||
data := Data{}
|
||||
|
||||
// decode values
|
||||
for key, value := range values {
|
||||
// handle maps
|
||||
if ok, err := DecodeMapValue(key, value, data); err != nil {
|
||||
return errors.Wrap(err, "failed to decode map value")
|
||||
} else if ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// handle slices
|
||||
if ok, err := DecodeRegexValue(key, value, RegexProduct, data, ParameterItem); err != nil {
|
||||
return errors.Wrap(err, "failed to decode regex value")
|
||||
} else if ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// default
|
||||
data[key] = value[0]
|
||||
}
|
||||
|
||||
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
WeaklyTypedInput: true,
|
||||
Result: &target,
|
||||
TagName: "gtag",
|
||||
IgnoreUntaggedFields: true,
|
||||
Squash: true,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to weakly decode query")
|
||||
}
|
||||
|
||||
if err := decoder.Decode(data); err != nil {
|
||||
return errors.Wrap(err, "failed to weakly decode query")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DecodeMapValue(k string, v []string, data Data) (bool, error) {
|
||||
if strings.Contains(k, ".") {
|
||||
parts := strings.Split(k, ".")
|
||||
if _, ok := data[parts[0]]; !ok {
|
||||
data[parts[0]] = map[string]any{}
|
||||
}
|
||||
if value, ok := data[parts[0]].(map[string]any); ok && len(v) > 0 {
|
||||
val := v[0]
|
||||
// gracefully try to unescape value
|
||||
if out, err := url.QueryUnescape(val); err == nil {
|
||||
val = out
|
||||
}
|
||||
value[strings.Join(parts[1:], ".")] = val
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// DecodeRegexValue e.g. `pr1=idSKU_123456` = map["pr"][]map["id"]="SKU_123456"
|
||||
func DecodeRegexValue(k string, v []string, r *regexp.Regexp, data Data, key string) (bool, error) {
|
||||
if r.MatchString(k) {
|
||||
value, err := DecodeObjectValue(v[0])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if value != nil {
|
||||
v, ok := data[key].([]map[string]any)
|
||||
if !ok {
|
||||
v = []map[string]any{}
|
||||
}
|
||||
v = append(v, value)
|
||||
data[key] = v
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// DecodeObjectValue e.g. `idSKU_123456` = map["id"]="SKU_123456"
|
||||
func DecodeObjectValue(s string) (map[string]any, error) {
|
||||
if len(s) == 0 {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
ret := map[string]any{}
|
||||
for _, part := range strings.Split(s, "~") {
|
||||
val := part[2:]
|
||||
// gracefully try to unescape value
|
||||
if out, err := url.QueryUnescape(val); err == nil {
|
||||
val = out
|
||||
}
|
||||
ret[part[0:2]] = val
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
155
pkg/encoding/gtag/decode_test.go
Normal file
155
pkg/encoding/gtag/decode_test.go
Normal file
@ -0,0 +1,155 @@
|
||||
package gtag_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
testingx "github.com/foomo/go/testing"
|
||||
tagx "github.com/foomo/go/testing/tag"
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
t.Parallel()
|
||||
testingx.Tags(t, tagx.Short)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "page_view",
|
||||
args: "v=2&tid=G-F9XM71K45T>m=45he5641v9184715813z89184708445za204zb9184708445&_p=1749196701069&gcs=G111&gcd=13r3r3r2r5l1&npa=0&dma_cps=syphamo&dma=1&tag_exp=101509157~103116026~103200004~103233427~103351869~103351871~104653070~104653072~104661466~104661468~104698127~104698129&cid=584335997.1746564151&ecid=1548980841&ul=en-us&sr=1728x1117&lps=1&_fplc=0&ur=DE&uaa=arm&uab=64&uafvl=Chromium%3B136.0.7103.114%7CGoogle%2520Chrome%3B136.0.7103.114%7CNot.A%252FBrand%3B99.0.0.0&uamb=0&uam=&uap=macOS&uapv=15.5.0&uaw=0&are=1&frm=0&pscdl=noapi&ec_mode=a&_eu=AAAAAAQ&sst.rnd=506702095.1749196701&sst.etld=google.de&sst.gcsub=region1&sst.adr=1&sst.tft=1749196701069&sst.lpc=253076363&sst.navt=n&sst.ude=1&sst.sw_exp=1&_s=13&dl=https%3A%2F%2Fsesamy.bestbytes.com%2F%3Futm_source%3Dgoogle%26utm_medium%3Ddemandgen%26utm_campaign%3Ddemandgenprodukte%26utm_id%3D22133718417%26utm_content%3D%26utm_term%3D%26gad_source%3D1%26gad_campaignid%3D22133718417%26gclid%3DCj0KCQjwlrvBBhDnARIsAHEQgOSQ%26gtm_debug%3D1747981544829&dt=Sesamy%20Demo%20Page&sid=1749196701&sct=41&seg=1&dr=https%3A%2F%2Fbestbytes.cloudflareaccess.com%2F&_tu=DA&en=page_view&_et=12397&tfd=285810&richsstsse",
|
||||
want: `{"consent":{"google_consent_status":"G111","google_consent_default":"13r3r3r2r5l1"},"campaign":{},"ecommerce":{},"client_hints":{"screen_resolution":"1728x1117","user_language":"en-us","user_agent_architecture":"arm","user_agent_bitness":"64","user_agent_full_version_list":"Chromium;136.0.7103.114|Google%20Chrome;136.0.7103.114|Not.A%2FBrand;99.0.0.0","user_agent_mobile":"0","user_agent_model":"","user_agent_platform":"macOS","user_agent_platform_version":"15.5.0","user_agent_wow_64":"0","user_region":"DE"},"protocol_version":"2","tracking_id":"G-F9XM71K45T","gtmhash_info":"45he5641v9184715813z89184708445za204zb9184708445","client_id":"584335997.1746564151","richsstsse":"","document_location":"https://sesamy.bestbytes.com/?utm_source=google\u0026utm_medium=demandgen\u0026utm_campaign=demandgenprodukte\u0026utm_id=22133718417\u0026utm_content=\u0026utm_term=\u0026gad_source=1\u0026gad_campaignid=22133718417\u0026gclid=Cj0KCQjwlrvBBhDnARIsAHEQgOSQ\u0026gtm_debug=1747981544829","document_title":"Sesamy Demo Page","document_referrer":"https://bestbytes.cloudflareaccess.com/","event_name":"page_view","session_id":"1749196701","non_personalized_ads":"0","sst":{"adr":"1","rnd":"506702095.1749196701","etld":"google.de","gcsub":"region1","tft":"1749196701069","ude":"1","lpc":"253076363","navt":"n","sw_exp":"1"}}`,
|
||||
},
|
||||
{
|
||||
name: "add_to_cart",
|
||||
args: "v=2&tid=G-F9XM71K45T>m=45he5641v9184715813z89184708445za204zb9184708445&_p=1749196701069&gcs=G111&gcd=13r3r3r2r5l1&npa=0&dma_cps=syphamo&dma=1&tag_exp=101509157~103116026~103200004~103233427~103351869~103351871~104653070~104653072~104661466~104661468~104698127~104698129&cid=584335997.1746564151&ecid=1548980841&ul=en-us&sr=1728x1117&lps=1&_fplc=0&ur=DE&uaa=arm&uab=64&uafvl=Chromium%3B136.0.7103.114%7CGoogle%2520Chrome%3B136.0.7103.114%7CNot.A%252FBrand%3B99.0.0.0&uamb=0&uam=&uap=macOS&uapv=15.5.0&uaw=0&are=1&frm=0&pscdl=noapi&ec_mode=a&_eu=AAAAAAQ&sst.rnd=506702095.1749196701&sst.etld=google.de&sst.gcsub=region1&sst.adr=1&sst.tft=1749196701069&sst.lpc=253076363&sst.navt=n&sst.ude=1&sst.sw_exp=1&_s=14&cu=USD&sid=1749196701&sct=41&seg=1&dl=https%3A%2F%2Fsesamy.bestbytes.com%2F%3Futm_source%3Dgoogle%26utm_medium%3Ddemandgen%26utm_campaign%3Ddemandgenprodukte%26utm_id%3D22133718417%26utm_content%3D%26utm_term%3D%26gad_source%3D1%26gad_campaignid%3D22133718417%26gclid%3DCj0KCQjwlrvBBhDnARIsAHEQgOSQ%26gtm_debug%3D1747981544829&dr=https%3A%2F%2Fbestbytes.cloudflareaccess.com%2F&dt=Sesamy%20Demo%20Page&_tu=DA&en=add_to_cart&_c=1&pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Merchandise%20Store~cpSUMMER_FUN~ds2.22~lp0~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20Products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr10.01~qt3&epn.value=30.03&_et=3110&tfd=395426&richsstsse",
|
||||
want: `{"consent":{"google_consent_status":"G111","google_consent_default":"13r3r3r2r5l1"},"campaign":{},"ecommerce":{"currency":"USD","items":[{"affiliation":"Google Merchandise Store","coupon":"SUMMER_FUN","discount":"2.22","item_brand":"Google","item_category":"Apparel","item_category2":"Adult","item_category3":"Shirts","item_category4":"Crew","item_category5":"Short sleeve","item_id":"SKU_12345","item_list_id":"related_products","item_list_name":"Related Products","item_name":"Stan and Friends Tee","item_variant":"green","item_list_position":"0","location_id":"ChIJIQBpAG2ahYAR_6128GcTUEo","price":"10.01","quantity":"3"}],"is_conversion":"1"},"client_hints":{"screen_resolution":"1728x1117","user_language":"en-us","user_agent_architecture":"arm","user_agent_bitness":"64","user_agent_full_version_list":"Chromium;136.0.7103.114|Google%20Chrome;136.0.7103.114|Not.A%2FBrand;99.0.0.0","user_agent_mobile":"0","user_agent_model":"","user_agent_platform":"macOS","user_agent_platform_version":"15.5.0","user_agent_wow_64":"0","user_region":"DE"},"protocol_version":"2","tracking_id":"G-F9XM71K45T","gtmhash_info":"45he5641v9184715813z89184708445za204zb9184708445","client_id":"584335997.1746564151","richsstsse":"","document_location":"https://sesamy.bestbytes.com/?utm_source=google\u0026utm_medium=demandgen\u0026utm_campaign=demandgenprodukte\u0026utm_id=22133718417\u0026utm_content=\u0026utm_term=\u0026gad_source=1\u0026gad_campaignid=22133718417\u0026gclid=Cj0KCQjwlrvBBhDnARIsAHEQgOSQ\u0026gtm_debug=1747981544829","document_title":"Sesamy Demo Page","document_referrer":"https://bestbytes.cloudflareaccess.com/","event_name":"add_to_cart","event_parameter_number":{"value":"30.03"},"session_id":"1749196701","non_personalized_ads":"0","sst":{"adr":"1","rnd":"506702095.1749196701","etld":"google.de","gcsub":"region1","tft":"1749196701069","ude":"1","lpc":"253076363","navt":"n","sw_exp":"1"}}`,
|
||||
},
|
||||
{
|
||||
name: "select_item",
|
||||
args: "v=2&tid=G-F9XM71K45T>m=45he5641v9184715813z89184708445za204zb9184708445&_p=1749196701069&gcs=G111&gcd=13r3r3r2r5l1&npa=0&dma_cps=syphamo&dma=1&tag_exp=101509157~103116026~103200004~103233427~103351869~103351871~104653070~104653072~104661466~104661468~104698127~104698129&cid=584335997.1746564151&ecid=1548980841&ul=en-us&sr=1728x1117&lps=1&_fplc=0&ur=DE&uaa=arm&uab=64&uafvl=Chromium%3B136.0.7103.114%7CGoogle%2520Chrome%3B136.0.7103.114%7CNot.A%252FBrand%3B99.0.0.0&uamb=0&uam=&uap=macOS&uapv=15.5.0&uaw=0&are=1&frm=0&pscdl=noapi&_eu=AAAAAAQ&sst.rnd=506702095.1749196701&sst.etld=google.de&sst.gcsub=region1&sst.adr=1&sst.tft=1749196701069&sst.lpc=253076363&sst.navt=n&sst.ude=1&sst.sw_exp=1&_s=15&sid=1749196701&sct=41&seg=1&dl=https%3A%2F%2Fsesamy.bestbytes.com%2F%3Futm_source%3Dgoogle%26utm_medium%3Ddemandgen%26utm_campaign%3Ddemandgenprodukte%26utm_id%3D22133718417%26utm_content%3D%26utm_term%3D%26gad_source%3D1%26gad_campaignid%3D22133718417%26gclid%3DCj0KCQjwlrvBBhDnARIsAHEQgOSQ%26gtm_debug%3D1747981544829&dr=https%3A%2F%2Fbestbytes.cloudflareaccess.com%2F&dt=Sesamy%20Demo%20Page&_tu=DA&en=select_item&pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Merchandise%20Store~cpSUMMER_FUN~ds2.22~lp0~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20Products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr10.01~qt3&ep.item_list_id=related_products&ep.item_list_name=Related%20products&_et=4363&tfd=436740&richsstsse",
|
||||
want: `{"consent":{"google_consent_status":"G111","google_consent_default":"13r3r3r2r5l1"},"campaign":{},"ecommerce":{"items":[{"affiliation":"Google Merchandise Store","coupon":"SUMMER_FUN","discount":"2.22","item_brand":"Google","item_category":"Apparel","item_category2":"Adult","item_category3":"Shirts","item_category4":"Crew","item_category5":"Short sleeve","item_id":"SKU_12345","item_list_id":"related_products","item_list_name":"Related Products","item_name":"Stan and Friends Tee","item_variant":"green","item_list_position":"0","location_id":"ChIJIQBpAG2ahYAR_6128GcTUEo","price":"10.01","quantity":"3"}]},"client_hints":{"screen_resolution":"1728x1117","user_language":"en-us","user_agent_architecture":"arm","user_agent_bitness":"64","user_agent_full_version_list":"Chromium;136.0.7103.114|Google%20Chrome;136.0.7103.114|Not.A%2FBrand;99.0.0.0","user_agent_mobile":"0","user_agent_model":"","user_agent_platform":"macOS","user_agent_platform_version":"15.5.0","user_agent_wow_64":"0","user_region":"DE"},"protocol_version":"2","tracking_id":"G-F9XM71K45T","gtmhash_info":"45he5641v9184715813z89184708445za204zb9184708445","client_id":"584335997.1746564151","richsstsse":"","document_location":"https://sesamy.bestbytes.com/?utm_source=google\u0026utm_medium=demandgen\u0026utm_campaign=demandgenprodukte\u0026utm_id=22133718417\u0026utm_content=\u0026utm_term=\u0026gad_source=1\u0026gad_campaignid=22133718417\u0026gclid=Cj0KCQjwlrvBBhDnARIsAHEQgOSQ\u0026gtm_debug=1747981544829","document_title":"Sesamy Demo Page","document_referrer":"https://bestbytes.cloudflareaccess.com/","event_name":"select_item","event_parameter":{"item_list_id":"related_products","item_list_name":"Related products"},"session_id":"1749196701","non_personalized_ads":"0","sst":{"adr":"1","rnd":"506702095.1749196701","etld":"google.de","gcsub":"region1","tft":"1749196701069","ude":"1","lpc":"253076363","navt":"n","sw_exp":"1"}}`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
var event gtag.Payload
|
||||
require.NoError(t, gtag.DecodeQuery(tt.args, &event))
|
||||
|
||||
out, err := json.Marshal(event)
|
||||
require.NoError(t, err)
|
||||
|
||||
if !assert.JSONEq(t, tt.want, string(out)) {
|
||||
t.Log(string(out))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeMapValue(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "v=2",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "ep.foo=bar",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "ep.foo.bar=bar",
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
d := gtag.Data{}
|
||||
values, err := url.ParseQuery(tt.name)
|
||||
require.NoError(t, err)
|
||||
|
||||
for k, v := range values {
|
||||
if got, err := gtag.DecodeMapValue(k, v, d); assert.NoError(t, err) && got != tt.want {
|
||||
t.Errorf("DecodeMapValue() = %v, want %v", got, tt.want)
|
||||
t.Log(d)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeProductValue(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "v=2",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Merchandise%20Store~cpSUMMER_FUN~ds2.22~lp0~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20Products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr9.99~qt1",
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
d := gtag.Data{}
|
||||
values, err := url.ParseQuery(tt.name)
|
||||
require.NoError(t, err)
|
||||
for k, v := range values {
|
||||
if got, err := gtag.DecodeRegexValue(k, v, gtag.RegexProduct, d, "pr"); assert.NoError(t, err) && got != tt.want {
|
||||
t.Errorf("decodeMapValue() = %v, want %v", got, tt.want)
|
||||
t.Log(d)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeObjectValue(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
want map[string]any
|
||||
}{
|
||||
{
|
||||
name: "",
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "idSKU_12345~nmStan%20and%20Friends%20Tee~qt1",
|
||||
want: map[string]any{
|
||||
"id": "SKU_12345",
|
||||
"nm": "Stan and Friends Tee",
|
||||
"qt": "1",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got, err := gtag.DecodeObjectValue(tt.name); assert.NoError(t, err) && !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("decodeObjectValue() = %v, want %v", got, tt.want)
|
||||
t.Log(got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
31
pkg/encoding/gtag/ecommerce.go
Normal file
31
pkg/encoding/gtag/ecommerce.go
Normal file
@ -0,0 +1,31 @@
|
||||
package gtag
|
||||
|
||||
import (
|
||||
"github.com/foomo/gostandards/iso4217"
|
||||
)
|
||||
|
||||
type ECommerce struct {
|
||||
// Currency Code. ISO 4217
|
||||
// Example: JPY
|
||||
Currency *iso4217.Currency `json:"currency,omitempty" gtag:"cu,omitempty"`
|
||||
// Example:
|
||||
Items []*Item `json:"items,omitempty" gtag:"pr,omitempty"`
|
||||
// Promotion Impression/Click Tracking. Promotion Id
|
||||
// Example: summer-offer
|
||||
PromotionID *string `json:"promotion_id,omitempty" gtag:"pi,omitempty"`
|
||||
// Promotion Impression/Click Tracking. Promotion Name
|
||||
// Example: summer-offer
|
||||
PromotionName *string `json:"promotion_name,omitempty" gtag:"pn,omitempty"`
|
||||
// Promotion Impression/Click Tracking. Creative Name
|
||||
// Example: red-car
|
||||
// CreativeName *string `json:"//,omitempty" gtag:"cn,omitempty"`
|
||||
// Promotion Impression/Click Tracking. Promotion Slot / Position
|
||||
// Example: slide-3
|
||||
// CreativeSlot *string `json:"//,omitempty" gtag:"cs,omitempty"`
|
||||
// Google Place ID: Refer to: https://developers.google.com/maps/documentation/places/web-service/place-id . Seems to be inherited from Firebase, not sure about the current use on GA4
|
||||
// Example: ChIJiyj437sx3YAR9kUWC8QkLzQ
|
||||
LocationID *string `json:"location_id,omitempty" gtag:"lo,omitempty"`
|
||||
// If the current event is set as a conversion on the admin interacted the evfent will have this value present
|
||||
// Example: 1
|
||||
IsConversion *string `json:"is_conversion,omitempty" gtag:"_c,omitempty"`
|
||||
}
|
||||
@ -1,42 +1,55 @@
|
||||
package v2
|
||||
package gtag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func Encode(input *Event) (url.Values, io.Reader, error) {
|
||||
func Encode(payload *Payload) (url.Values, io.Reader, error) {
|
||||
var richsstsse bool
|
||||
// NOTE: `richsstsse` seems to be last parameter in the query to let's ensure it stays that way
|
||||
if input.Richsstsse != nil {
|
||||
if payload.Richsstsse != nil {
|
||||
richsstsse = true
|
||||
input.Richsstsse = nil
|
||||
payload.Richsstsse = nil
|
||||
}
|
||||
|
||||
a, err := json.Marshal(input)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
remain := payload.Remain
|
||||
payload.Remain = nil
|
||||
|
||||
data := Data{}
|
||||
if err := json.Unmarshal(a, &data); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to decode into map")
|
||||
var json = jsoniter.Config{
|
||||
TagKey: "gtag",
|
||||
EscapeHTML: false,
|
||||
MarshalFloatWith6Digits: true, // will lose precession
|
||||
ObjectFieldMustBeSimpleString: true, // do not unescape object field
|
||||
}.Froze()
|
||||
|
||||
jsonBytes, err := json.Marshal(&payload)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to marshall payload")
|
||||
}
|
||||
if err := json.Unmarshal(jsonBytes, &data); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to unmarshall payload")
|
||||
}
|
||||
|
||||
for s, a := range remain {
|
||||
data[s] = a
|
||||
}
|
||||
|
||||
ret := url.Values{}
|
||||
for k, v := range data {
|
||||
switch t := v.(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
for key, value := range t {
|
||||
switch tt := value.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
ret[fmt.Sprintf("%s%d", k, key+1)] = []string{EncodeObjectValue(tt)}
|
||||
default:
|
||||
panic("unhandled")
|
||||
@ -46,7 +59,7 @@ func Encode(input *Event) (url.Values, io.Reader, error) {
|
||||
for key, value := range t {
|
||||
ret[fmt.Sprintf("%s.%s", k, key)] = []string{value}
|
||||
}
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
for key, value := range t {
|
||||
ret[fmt.Sprintf("%s.%s", k, key)] = []string{fmt.Sprintf("%v", value)}
|
||||
}
|
||||
@ -54,6 +67,10 @@ func Encode(input *Event) (url.Values, io.Reader, error) {
|
||||
ret[k] = []string{*t}
|
||||
case string:
|
||||
ret[k] = []string{t}
|
||||
case interface{ String() string }:
|
||||
ret[k] = []string{t.String()}
|
||||
case nil:
|
||||
continue
|
||||
default:
|
||||
panic("unhandled")
|
||||
}
|
||||
@ -61,7 +78,7 @@ func Encode(input *Event) (url.Values, io.Reader, error) {
|
||||
|
||||
var body []string
|
||||
var reader io.Reader
|
||||
maxQueryLength := 2048 //
|
||||
maxQueryLength := 2048
|
||||
if richsstsse {
|
||||
maxQueryLength -= len("&richsstsse")
|
||||
}
|
||||
53
pkg/encoding/gtag/encode_test.go
Normal file
53
pkg/encoding/gtag/encode_test.go
Normal file
@ -0,0 +1,53 @@
|
||||
package gtag_test
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
testingx "github.com/foomo/go/testing"
|
||||
tagx "github.com/foomo/go/testing/tag"
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEncode(t *testing.T) {
|
||||
t.Parallel()
|
||||
testingx.Tags(t, tagx.Short)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
}{
|
||||
{
|
||||
name: "page_view",
|
||||
args: "v=2&tid=G-F9XM71K45T>m=45he5641v9184715813z89184708445za204zb9184708445&_p=1749196701069&gcs=G100&gcd=13p3p3p2p5l1&npa=1&dma_cps=-&dma=1&tag_exp=101509157~103116026~103200004~103233427~103351869~103351871~104653070~104653072~104661466~104661468~104698127~104698129>m_up=1&cid=1174285007.1749196701&ecid=1548980841&ul=en-us&sr=1728x1117&lps=1&_fplc=0&ur=DE&uaa=arm&uab=64&uafvl=Chromium%3B136.0.7103.114%7CGoogle%2520Chrome%3B136.0.7103.114%7CNot.A%252FBrand%3B99.0.0.0&uamb=0&uam=&uap=macOS&uapv=15.5.0&uaw=0&are=1&frm=0&pscdl=denied&sst.rnd=506702095.1749196701&sst.etld=google.de&sst.gcsub=region1&sst.adr=1&sst.tft=1749196701069&sst.lpc=253076363&sst.navt=n&sst.ude=1&_s=3&sid=1749196701&sct=1&seg=0&dl=https%3A%2F%2Fsesamy.bestbytes.com%2F%3Futm_source%3Dgoogle%26utm_medium%3Ddemandgen%26utm_campaign%3Ddemandgenprodukte%26utm_id%3D22133718417%26utm_content%3D%26utm_term%3D%26gad_source%3D1%26gad_campaignid%3D22133718417%26gclid%3DCj0KCQjwlrvBBhDnARIsAHEQgOSQ%26gtm_debug%3D1747981544829&dr=https%3A%2F%2Fbestbytes.cloudflareaccess.com%2F&dt=Sesamy&_tu=DA&en=scroll&_et=NaN&epn.percent_scrolled=90&tfd=8412&richsstsse",
|
||||
},
|
||||
{
|
||||
name: "add_to_cart",
|
||||
args: "v=2&tid=G-F9XM71K45T>m=45he5641v9184715813z89184708445za204zb9184708445&_p=1749196701069&gcs=G100&gcd=13p3p3p2p5l1&npa=1&dma_cps=-&dma=1&tag_exp=101509157~103116026~103200004~103233427~103351869~103351871~104653070~104653072~104661466~104661468~104698127~104698129>m_up=1&cid=1174285007.1749196701&ecid=1548980841&ul=en-us&sr=1728x1117&lps=1&_fplc=0&ur=DE&uaa=arm&uab=64&uafvl=Chromium%3B136.0.7103.114%7CGoogle%2520Chrome%3B136.0.7103.114%7CNot.A%252FBrand%3B99.0.0.0&uamb=0&uam=&uap=macOS&uapv=15.5.0&uaw=0&are=1&frm=0&pscdl=denied&ec_mode=a&sst.rnd=506702095.1749196701&sst.etld=google.de&sst.gcsub=region1&sst.adr=1&sst.tft=1749196701069&sst.lpc=253076363&sst.navt=n&sst.ude=1&_s=4&cu=USD&sid=1749196701&sct=1&seg=0&dl=https%3A%2F%2Fsesamy.bestbytes.com%2F%3Futm_source%3Dgoogle%26utm_medium%3Ddemandgen%26utm_campaign%3Ddemandgenprodukte%26utm_id%3D22133718417%26utm_content%3D%26utm_term%3D%26gad_source%3D1%26gad_campaignid%3D22133718417%26gclid%3DCj0KCQjwlrvBBhDnARIsAHEQgOSQ%26gtm_debug%3D1747981544829&dr=https%3A%2F%2Fbestbytes.cloudflareaccess.com%2F&dt=Sesamy&_tu=DA&en=add_to_cart&_c=1&pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Merchandise%20Store~cpSUMMER_FUN~ds2.22~lp0~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20Products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr10.01~qt3&epn.value=30.03&_et=8715&tfd=53322&richsstsse",
|
||||
},
|
||||
{
|
||||
name: "purchase",
|
||||
args: "v=2&tid=G-F9XM71K45T>m=45he5641v9184715813z89184708445za204zb9184708445&_p=1749196701069&gcs=G100&gcd=13p3p3p2p5l1&npa=1&dma_cps=-&dma=1&tag_exp=101509157~103116026~103200004~103233427~103351869~103351871~104653070~104653072~104661466~104661468~104698127~104698129>m_up=1&cid=1174285007.1749196701&ecid=1548980841&ul=en-us&sr=1728x1117&lps=1&_fplc=0&ur=DE&uaa=arm&uab=64&uafvl=Chromium%3B136.0.7103.114%7CGoogle%2520Chrome%3B136.0.7103.114%7CNot.A%252FBrand%3B99.0.0.0&uamb=0&uam=&uap=macOS&uapv=15.5.0&uaw=0&are=1&frm=0&pscdl=denied&ec_mode=a&sst.rnd=506702095.1749196701&sst.etld=google.de&sst.gcsub=region1&sst.adr=1&sst.tft=1749196701069&sst.lpc=253076363&sst.navt=n&sst.ude=1&_s=4&cu=USD&sid=1749196701&sct=1&seg=0&dl=https%3A%2F%2Fsesamy.bestbytes.com%2F%3Futm_source%3Dgoogle%26utm_medium%3Ddemandgen%26utm_campaign%3Ddemandgenprodukte%26utm_id%3D22133718417%26utm_content%3D%26utm_term%3D%26gad_source%3D1%26gad_campaignid%3D22133718417%26gclid%3DCj0KCQjwlrvBBhDnARIsAHEQgOSQ%26gtm_debug%3D1747981544829&dr=https%3A%2F%2Fbestbytes.cloudflareaccess.com%2F&dt=Sesamy&_tu=DA&en=add_to_cart&_c=1&pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Merchandise%20Store~cpSUMMER_FUN~ds2.22~lp0~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20Products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr10.01~qt3&epn.value=30.03&_et=8715&tfd=53322&richsstsse",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
values, err := url.ParseQuery(tt.args)
|
||||
require.NoError(t, err)
|
||||
var event gtag.Payload
|
||||
|
||||
require.NoError(t, gtag.Decode(values, &event))
|
||||
assert.NotEmpty(t, event.Remain)
|
||||
|
||||
if actual, _, err := gtag.Encode(&event); assert.NoError(t, err) {
|
||||
if !assert.Len(t, actual.Encode(), len(values.Encode())) {
|
||||
t.Logf("expected: %s", values.Encode())
|
||||
t.Logf("actual: %s", actual.Encode())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
16
pkg/encoding/gtag/getset.go
Normal file
16
pkg/encoding/gtag/getset.go
Normal file
@ -0,0 +1,16 @@
|
||||
package gtag
|
||||
|
||||
func Set[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
|
||||
func Get[T any](v *T) T {
|
||||
return *v
|
||||
}
|
||||
|
||||
func GetDefault[T any](v *T, fallback T) T {
|
||||
if v == nil {
|
||||
return fallback
|
||||
}
|
||||
return *v
|
||||
}
|
||||
52
pkg/encoding/gtag/item.go
Normal file
52
pkg/encoding/gtag/item.go
Normal file
@ -0,0 +1,52 @@
|
||||
package gtag
|
||||
|
||||
type Item struct {
|
||||
// Example: Foo Marketplace
|
||||
Affiliation *string `json:"affiliation,omitempty" gtag:"af,omitempty"`
|
||||
// Example: 50%OFF
|
||||
Coupon *string `json:"coupon,omitempty" gtag:"cp,omitempty"`
|
||||
// The name of the promotional creative.
|
||||
// Example: summer_banner2
|
||||
CreativeName *string `json:"creative_name,omitempty" gtag:"cn,omitempty"`
|
||||
// The name of the promotional creative slot associated with the item.
|
||||
// Example: featured_app_1
|
||||
CreativeSlot *string `json:"creative_slot,omitempty" gtag:"cs,omitempty"`
|
||||
// Example: 10.00
|
||||
Discount *string `json:"discount,omitempty" gtag:"ds,omitempty"`
|
||||
// Example: Google
|
||||
ItemBrand *string `json:"item_brand,omitempty" gtag:"br,omitempty"`
|
||||
// Example: men
|
||||
ItemCategory *string `json:"item_category,omitempty" gtag:"ca,omitempty"`
|
||||
// Example: t-shirts
|
||||
ItemCategory2 *string `json:"item_category2,omitempty" gtag:"c2,omitempty"`
|
||||
// Example: men
|
||||
ItemCategory3 *string `json:"item_category3,omitempty" gtag:"c3,omitempty"`
|
||||
// Example: men
|
||||
ItemCategory4 *string `json:"item_category4,omitempty" gtag:"c4,omitempty"`
|
||||
// Example: men
|
||||
ItemCategory5 *string `json:"item_category5,omitempty" gtag:"c5,omitempty"`
|
||||
// Example: 12345
|
||||
ItemID *string `json:"item_id,omitempty" gtag:"id,omitempty"`
|
||||
// Example: id-mens-123
|
||||
ItemListID *string `json:"item_list_id,omitempty" gtag:"li,omitempty"`
|
||||
// Example: cross-selling: mens
|
||||
ItemListName *string `json:"item_list_name,omitempty" gtag:"ln,omitempty"`
|
||||
// Example: Stan and Friends Tee
|
||||
ItemName *string `json:"item_name,omitempty" gtag:"nm,omitempty"`
|
||||
// Example: Yellow
|
||||
ItemVariant *string `json:"item_variant,omitempty" gtag:"va,omitempty"`
|
||||
// Example: 10 FIXME
|
||||
ItemListPosition *string `json:"item_list_position,omitempty" gtag:"lp,omitempty"`
|
||||
// Example: ChIJIQBpAG2ahYAR_6128GcTUEo
|
||||
LocationID *string `json:"location_id,omitempty" gtag:"lo,omitempty"`
|
||||
// Example: 123.45
|
||||
Price *string `json:"price,omitempty" gtag:"pr,omitempty"`
|
||||
// The ID of the promotion associated with the item.
|
||||
// Example: P_12345
|
||||
PromotionID *string `json:"promotion_id,omitempty" gtag:"pi,omitempty"`
|
||||
// The name of the promotion associated with the item.
|
||||
// Example: Summer Sale
|
||||
PromotionName *string `json:"promotion_name,omitempty" gtag:"pn,omitempty"`
|
||||
// Example: 1
|
||||
Quantity *string `json:"quantity,omitempty" gtag:"qt,omitempty"`
|
||||
}
|
||||
163
pkg/encoding/gtag/payload.go
Normal file
163
pkg/encoding/gtag/payload.go
Normal file
@ -0,0 +1,163 @@
|
||||
package gtag
|
||||
|
||||
import (
|
||||
"github.com/foomo/sesamy-go/pkg/sesamy"
|
||||
)
|
||||
|
||||
// Payload https://www.thyngster.com/ga4-measurement-protocol-cheatsheet/
|
||||
type Payload struct {
|
||||
Consent `json:"consent" gtag:",inline,squash"`
|
||||
Campaign `json:"campaign" gtag:",inline,squash"`
|
||||
ECommerce `json:"ecommerce" gtag:",inline,squash"`
|
||||
ClientHints `json:"client_hints" gtag:",inline,squash"`
|
||||
|
||||
// --- Request parameters ---
|
||||
|
||||
// Defines que current protocol version being used.
|
||||
// Example: 2
|
||||
ProtocolVersion *string `json:"protocol_version,omitempty" gtag:"v,omitempty"`
|
||||
// Current Stream ID / Measurement ID
|
||||
// Example: G-THYNGSTER
|
||||
TrackingID *string `json:"tracking_id,omitempty" gtag:"tid,omitempty"`
|
||||
// If the current hit is coming was generated from GTM, it will contain a hash of current GTM/GTAG config
|
||||
// Example: 2oear0
|
||||
GTMHashInfo *string `json:"gtmhash_info,omitempty" gtag:"gtm,omitempty"`
|
||||
// Current Document Hostname
|
||||
// Exampple: www.analytics-debugger.com
|
||||
// DocumentHostname *string `json:"document_hostname,omitempty" gtag:"dh,omitempty"`
|
||||
// Google Analytics Client Id
|
||||
// Example: 281344611.1635634925
|
||||
ClientID *string `json:"client_id,omitempty" gtag:"cid,omitempty"`
|
||||
// Current hits counter for the current page load
|
||||
// Example: 1
|
||||
// HitCounter *string `json:"hit_counter,omitempty" gtag:"_s,omitempty"`
|
||||
// This is supposed to be to enrich the GA4 hits to send data to SGTM, at this point is always set as an empty value...
|
||||
Richsstsse *string `json:"richsstsse,omitempty" gtag:"richsstsse,omitempty"`
|
||||
|
||||
// --- Shared ---
|
||||
|
||||
// Actual page's Pathname. It does not include the hostname, quertyString or Fragment
|
||||
// Example: /hire-me
|
||||
DocumentLocation *string `json:"document_location,omitempty" gtag:"dl,omitempty"`
|
||||
// Actual page's Title
|
||||
// Example: Hire Me
|
||||
DocumentTitle *string `json:"document_title,omitempty" gtag:"dt,omitempty"`
|
||||
// Actual page's Referrer
|
||||
// Example:
|
||||
DocumentReferrer *string `json:"document_referrer,omitempty" gtag:"dr,omitempty"`
|
||||
// Unknown. Value ccd.{{HASH}}. The hash in based on various internal parameters. Some kind of usage hash.
|
||||
// Example: ccd.AAB
|
||||
// Z *string `json:"z,omitempty" gtag:"_z,omitempty"`
|
||||
// This is added when an event is generated from rules (from the admin). Actually is hash of the "GA4_EVENT" string
|
||||
// Example: Q
|
||||
// EventUsage *string `json:"event_usage,omitempty" gtag:"_eu,omitempty"`
|
||||
// Unknown
|
||||
// Example:
|
||||
// EventDebugID *string `json:"event_debug_id,omitempty" gtag:"edid,omitempty"`
|
||||
// If an event contains this parameters it won't be processed and it will show on on the debug View in GA4
|
||||
// Example: 1
|
||||
IsDebug *string `json:"is_debug,omitempty" gtag:"_dbg,omitempty"`
|
||||
// If the current request has a referrer, it will be ignored at processing level
|
||||
// Example: 1
|
||||
// IgnoreReferrer *string `json:"ignore_referrer,omitempty" gtag:"ir,omitempty"`
|
||||
// Traffic Type
|
||||
// Example: 1
|
||||
// TrafficType *string `json:"traffic_type,omitempty" gtag:"tt,omitempty"`
|
||||
// Will be set to 1 is the current page has a linker and this last one is valid
|
||||
// Example: 1
|
||||
// IsGoogleLinkerValid *string `json:"is_google_linker_valid,omitempty" gtag:"_glv,omitempty"`
|
||||
|
||||
// --- Event Parameters ---
|
||||
|
||||
// Current Payload Name.
|
||||
// Example: page_view
|
||||
EventName *sesamy.EventName `json:"event_name,omitempty" gtag:"en,omitempty"`
|
||||
// It's the total engagement time in milliseconds since the last event. The engagement time is measured only when the current page is visible and active ( ie: the browser window/tab must be active and visible ), for this GA4 uses the window.events: focus, blur, pageshow, pagehide and the document:visibilitychange, these will determine when the timer starts and pauses
|
||||
// Example: 1234
|
||||
// EngagementTime *string `json:"engagement_time,omitempty" gtag:"_et,omitempty"`
|
||||
// Defines a parameter for the current Payload
|
||||
// Example: ep.page_type: checkout
|
||||
EventParameter map[string]string `json:"event_parameter,omitempty" gtag:"ep,omitempty"`
|
||||
// Defines a parameter for the current Payload
|
||||
// Example: epn.plays_count: 42
|
||||
EventParameterNumber map[string]string `json:"event_parameter_number,omitempty" gtag:"epn,omitempty"`
|
||||
// External Event
|
||||
// ExternalEvent *string `json:"external_event,omitempty" gtag:"_ee,omitempty"`
|
||||
|
||||
// --- Session / User Related ---
|
||||
|
||||
// Current User ID
|
||||
// Example: 1635691016
|
||||
UserID *string `json:"user_id,omitempty" gtag:"uid,omitempty"`
|
||||
// Current Firebase ID
|
||||
// Example: HASHSAH
|
||||
// FirebaseID *string `json:"firebase_id,omitempty" gtag:"_fid,omitempty"`
|
||||
// GA4 Session Id. This comes from the GA4 Cookie. It may be different for each Stream ID Configured on the site
|
||||
// Example: 1635691016
|
||||
SessionID *string `json:"session_id,omitempty" gtag:"sid,omitempty"`
|
||||
// Count of sessions recorded by GA4. This value increases by one each time a new session is detected ( when the session expires )
|
||||
// Example: 10
|
||||
// SessionCount *string `json:"session_count,omitempty" gtag:"sct,omitempty"`
|
||||
// If the current user is engaged in any way, this value will be 1
|
||||
// Example:
|
||||
// SessionEngagment *string `json:"session_engagement,omitempty" gtag:"seg,omitempty"`
|
||||
// Defines an user Propery for the current Measurement ID
|
||||
// Example: up.is_premium_user: yes
|
||||
UserProperty map[string]string `json:"user_property,omitempty" gtag:"up,omitempty"`
|
||||
// Defines an user Propery for the current Measurement ID
|
||||
// Example:
|
||||
UserPropertyNumber map[string]string `json:"user_property_number,omitempty" gtag:"upn,omitempty"`
|
||||
// If the "_ga_THYNGSTER" cookie is not set, the first event will have this value present. This will internally create a new "first_visit" event on GA4. If this event is also a conversion the value will be "2" if not, will be "1"
|
||||
// Example: 1|2
|
||||
// FirstVisit *string `json:"first_visit,omitempty" gtag:"_fv,omitempty"`
|
||||
// If the "_ga_THYNGSTER" cookie last session time value is older than 1800 seconds, the current event will have this value present. This will internally create a new "session_start" event on GA4. If this event is also a conversion the value will be "2" if not, will be "1"
|
||||
// Example: 1|2
|
||||
// SessionStart *string `json:"session_start,omitempty" gtag:"_ss,omitempty"`
|
||||
// This seems to be related to the ServerSide hits, it's 0 if the FPLC Cookie is not present and to the current value if it's coming from a Cross Domain linker
|
||||
// Example: bVhVicbfiSXaGNxeawKaPlDQc9QXPD6bKcsn36Elden6wZNb7Q5X1iXlkTVP5iP3H3y76cgM3UIgHCaRsYfPoyLGlbiIYMPRjvnUU7KWbdWLagodzxjrlPnvaRZJkw
|
||||
// FirstPartyLinkerCookie *string `json:"first_party_linker_cookie,omitempty" gtag:"_fplc,omitempty"`
|
||||
// If the current user has a GA4 session cookie, but not a GA (_ga) client id cookie, this parameter will be added to the hit
|
||||
// Example: 1
|
||||
// NewSessionID *string `json:"new_session_id,omitempty" gtag:"_nsi,omitempty"`
|
||||
// You may find this parameter if using some vendor plugin o platform ( ie: using shopify integration or a prestashop plugin )
|
||||
// Example: jdhsd87
|
||||
// GoogleDeveloperID *string `json:"google_developer_id,omitempty" gtag:"gdid,omitempty"`
|
||||
|
||||
// --- Uncategorized / Missing Info ---
|
||||
|
||||
// Example: 1
|
||||
// GTMUp *string `json:"gtmup,omitempty" gtag:"gtm_up,omitempty"`
|
||||
// Documented values, 1,2,3: Not sure when it's added.
|
||||
// EuropeanConsentModeEnabledID *string `json:"european_consent_mode_enabled_id,omitempty" gtag:"_ecid,omitempty"`
|
||||
// Example:
|
||||
// UEI *string `json:"uei,omitempty" gtag:"_uei,omitempty"`
|
||||
// It's set when a Google Join is created/imported. Google Signals
|
||||
// Example: 1
|
||||
// CreateGoogleJoin *string `json:"create_google_join,omitempty" gtag:"_gaz,omitempty"`
|
||||
// Example: Redact Device Info. Need Investigation about functionality
|
||||
// RedactDeviceInfo *string `json:"redact_device_info,omitempty" gtag:"_rdi,omitempty"`
|
||||
// Geo Granularity. Need Investigation about functionality
|
||||
// GeoGranularity *string `json:"geo_granularity,omitempty" gtag:"_geo,omitempty"`
|
||||
// Sent on sites that implement the US Privacy User Signal Mechanism, sent if window.__uspapi is present and returning a value.
|
||||
// Example: 1YNY
|
||||
// USPrivacySignal *string `json:"usprivacy_signal,omitempty" gtag:"us_privacy,omitempty"`
|
||||
// Sent on sites that implements IAB GDPR-Transparency-and-Consent-Framework( TCFv2 ) Mechanism. sent if window.__tcfapi is present and returning a valid value.
|
||||
// Example: 1
|
||||
// GDPR *string `json:"gdpr,omitempty" gtag:"gdpr,omitempty"`
|
||||
// Sent on sites that implements IAB GDPR-Transparency-and-Consent-Framework( TCFv2 ) Mechanism. sent if window.__tcfapi is present and returning a valid value.
|
||||
// Example: CPfPdAAPfPdAAAHABBENCgCsAP_AAAAAAAAAI_tf_X__b3_j-_5___t0eY1f9_7__-0zjhfdl-8N3f_X_L8X_2M7vF36tq4KuR4Eu3LBIQdlHOHcTUmw6okVrzPsbk2cr7NKJ7PEmnMbeydYGH9_n1_z-ZKY7_____77__-____3_____-_f___5_3____f_V__97fn9_____9_P___9v__9__________3___gAAAJJQAYAAgj-GgAwABBH8VABgACCP5SADAAEEfx0AGAAII_kIAMAAQR_CQAYAAgj-IgAwABBH8ZABgACCP4A.f_gAAAAAAAAA
|
||||
// GDPRConsent *string `json:"gdprconsent,omitempty" gtag:"gdpr_consent,omitempty"`
|
||||
// Example: sypham
|
||||
NonPersonalizedAds *string `json:"non_personalized_ads,omitempty" gtag:"npa,omitempty"`
|
||||
// Example: 1
|
||||
// ARE *string `json:"are,omitempty" gtag:"are,omitempty"`
|
||||
// PrivacySandboxCookieDeprecationLabel *string `json:"privacy_sandbox_cookie_deprecation_label,omitempty" gtag:"pscdl,omitempty"`
|
||||
// A timestamp measuring the difference between the moment this parameter gets populated and the moment the navigation started on that particular page.
|
||||
// TFD *string `json:"tfd,omitempty" gtag:"tfd,omitempty"`
|
||||
SST *SST `json:"sst,omitempty" gtag:"sst,omitempty"`
|
||||
// PAE *string `json:"pae,omitempty" gtag:"pae,omitempty"`
|
||||
|
||||
// --- Unresolved ---
|
||||
|
||||
Remain map[string]any `json:"-" gtag:"-,omitempy,remain"`
|
||||
}
|
||||
28
pkg/encoding/gtag/sst.go
Normal file
28
pkg/encoding/gtag/sst.go
Normal file
@ -0,0 +1,28 @@
|
||||
package gtag
|
||||
|
||||
type SST struct {
|
||||
// Example: 1
|
||||
ADR *string `json:"adr,omitempty" gtag:"adr,omitempty"`
|
||||
// Example: 1---
|
||||
USPrivacy *string `json:"us_privacy,omitempty" gtag:"us_privacy,omitempty"`
|
||||
// Example: 542231386.1709295522
|
||||
RND *string `json:"rnd,omitempty" gtag:"rnd,omitempty"`
|
||||
// Example: google.de
|
||||
ETLD *string `json:"etld,omitempty" gtag:"etld,omitempty"`
|
||||
// Example: region1
|
||||
GCSub *string `json:"gcsub,omitempty" gtag:"gcsub,omitempty"`
|
||||
// Example: DE
|
||||
UC *string `json:"uc,omitempty" gtag:"uc,omitempty"`
|
||||
// Session start time, time first seen. Example: 1708250245344
|
||||
TFT *string `json:"tft,omitempty" gtag:"tft,omitempty"`
|
||||
// Example: 13l3l3l3l1
|
||||
GCD *string `json:"gcd,omitempty" gtag:"gcd,omitempty"`
|
||||
// Example: 0
|
||||
UDE *string `json:"ude,omitempty" gtag:"ude,omitempty"`
|
||||
// Example: 223090308
|
||||
LPC *string `json:"lpc,omitempty" gtag:"lpc,omitempty"`
|
||||
// Example: 223090308
|
||||
NAVT *string `json:"navt,omitempty" gtag:"navt,omitempty"`
|
||||
// Example: 1
|
||||
SWExp *string `json:"sw_exp,omitempty" gtag:"sw_exp,omitempty"`
|
||||
}
|
||||
@ -1,7 +1,8 @@
|
||||
package v2
|
||||
package gtag
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EncodeValues
|
||||
@ -13,7 +14,7 @@ func EncodeValues(values url.Values) string {
|
||||
richsstsse = true
|
||||
}
|
||||
|
||||
ret := values.Encode()
|
||||
ret := strings.ReplaceAll(values.Encode(), "+", "%20")
|
||||
|
||||
if richsstsse {
|
||||
ret += "&richsstsse"
|
||||
108
pkg/encoding/gtagencode/mpv2.go
Normal file
108
pkg/encoding/gtagencode/mpv2.go
Normal file
@ -0,0 +1,108 @@
|
||||
package gtagencode
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"strconv"
|
||||
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func MPv2(source gtag.Payload, target any) error {
|
||||
var sourceData map[string]any
|
||||
|
||||
out, err := json.Marshal(source)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal source")
|
||||
}
|
||||
if err = json.Unmarshal(out, &sourceData); err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshal source")
|
||||
}
|
||||
|
||||
// transform map to match mpv2 format
|
||||
targetData := map[string]any{
|
||||
"client_id": source.ClientID,
|
||||
"user_id": source.UserID,
|
||||
"session_id": source.SessionID,
|
||||
"non_personalized_ads": source.NonPersonalizedAds,
|
||||
"debug_mode": source.IsDebug,
|
||||
}
|
||||
|
||||
// consent
|
||||
targetConsentData := map[string]any{
|
||||
"ad_storage": mpv2.ConsentText(source.AdStorage()),
|
||||
"ad_user_data": mpv2.ConsentText(source.AdUserData()),
|
||||
"ad_personalization": mpv2.ConsentText(source.AdPersonalization()),
|
||||
"analytics_storage": mpv2.ConsentText(source.AnalyticsStorage()),
|
||||
}
|
||||
targetData["consent"] = targetConsentData
|
||||
|
||||
// combine user properties
|
||||
targetUserProperties := map[string]any{}
|
||||
if node, ok := sourceData["user_property"].(map[string]string); ok {
|
||||
for s, s2 := range node {
|
||||
targetUserProperties[s] = s2
|
||||
}
|
||||
}
|
||||
if node, ok := sourceData["user_property_number"].(map[string]string); ok {
|
||||
for s, s2 := range node {
|
||||
targetUserProperties[s] = s2
|
||||
}
|
||||
}
|
||||
targetData["user_properties"] = targetUserProperties
|
||||
|
||||
// transform event
|
||||
targetEventData := map[string]any{
|
||||
"name": source.EventName,
|
||||
}
|
||||
targetEventDataParams := map[string]any{}
|
||||
if value, ok := sourceData["document_title"]; ok {
|
||||
targetEventDataParams["page_title"] = value
|
||||
}
|
||||
if value, ok := sourceData["document_referrer"]; ok {
|
||||
targetEventDataParams["page_referrer"] = value
|
||||
}
|
||||
if value, ok := sourceData["document_location"]; ok {
|
||||
targetEventDataParams["page_location"] = value
|
||||
}
|
||||
if node, ok := sourceData["ecommerce"].(map[string]any); ok {
|
||||
maps.Copy(targetEventDataParams, node)
|
||||
}
|
||||
if node, ok := sourceData["event_parameter"].(map[string]any); ok {
|
||||
for s, s2 := range node {
|
||||
targetEventDataParams[s] = s2
|
||||
}
|
||||
}
|
||||
if node, ok := sourceData["event_parameter_number"].(map[string]any); ok {
|
||||
for s, s2 := range node {
|
||||
if value, err := strconv.ParseFloat(fmt.Sprintf("%s", s2), 64); err == nil {
|
||||
targetEventDataParams[s] = value
|
||||
} else {
|
||||
targetEventDataParams[s] = s2
|
||||
}
|
||||
}
|
||||
}
|
||||
targetEventData["params"] = targetEventDataParams
|
||||
targetData["events"] = []any{targetEventData}
|
||||
|
||||
// encode map to target entity
|
||||
enc, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
Result: target,
|
||||
TagName: "json",
|
||||
WeaklyTypedInput: true,
|
||||
IgnoreUntaggedFields: true,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create event encoder")
|
||||
}
|
||||
|
||||
if err := enc.Decode(targetData); err != nil {
|
||||
return errors.Wrap(err, "failed to encode event")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
76
pkg/encoding/gtagencode/mpv2_test.go
Normal file
76
pkg/encoding/gtagencode/mpv2_test.go
Normal file
@ -0,0 +1,76 @@
|
||||
package gtagencode_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/gtagencode"
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
|
||||
"github.com/foomo/sesamy-go/pkg/sesamy"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMPv2(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
source gtag.Payload
|
||||
want mpv2.Payload[any]
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "basic conversion",
|
||||
source: gtag.Payload{
|
||||
ClientID: gtag.Set("test-client"),
|
||||
UserID: gtag.Set("test-user"),
|
||||
SessionID: gtag.Set("test-session"),
|
||||
NonPersonalizedAds: gtag.Set("1"),
|
||||
IsDebug: gtag.Set("true"),
|
||||
EventName: gtag.Set(sesamy.EventName("page_view")),
|
||||
DocumentTitle: gtag.Set("Test Page"),
|
||||
DocumentLocation: gtag.Set("https://test.com"),
|
||||
DocumentReferrer: gtag.Set("https://referrer.com"),
|
||||
},
|
||||
want: mpv2.Payload[any]{
|
||||
ClientID: "test-client",
|
||||
UserID: "test-user",
|
||||
SessionID: "test-session",
|
||||
DebugMode: true,
|
||||
UserProperties: map[string]any{},
|
||||
Events: []sesamy.Event[any]{
|
||||
{
|
||||
Name: "page_view",
|
||||
Params: map[string]any{
|
||||
"page_title": "Test Page",
|
||||
"page_location": "https://test.com",
|
||||
"page_referrer": "https://referrer.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
Consent: &mpv2.ConsentData{
|
||||
AdStorage: gtag.Set(mpv2.ConsentGranted),
|
||||
AdUserData: gtag.Set(mpv2.ConsentGranted),
|
||||
AdPersonalization: gtag.Set(mpv2.ConsentGranted),
|
||||
AnalyticsStorage: gtag.Set(mpv2.ConsentGranted),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
var got mpv2.Payload[any]
|
||||
err := gtagencode.MPv2(tt.source, &got)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
15
pkg/encoding/mpv2/consent.go
Normal file
15
pkg/encoding/mpv2/consent.go
Normal file
@ -0,0 +1,15 @@
|
||||
package mpv2
|
||||
|
||||
type Consent string
|
||||
|
||||
const (
|
||||
ConsentDenied Consent = "DENIED"
|
||||
ConsentGranted Consent = "GRANTED"
|
||||
)
|
||||
|
||||
func ConsentText(v bool) Consent {
|
||||
if v {
|
||||
return ConsentGranted
|
||||
}
|
||||
return ConsentDenied
|
||||
}
|
||||
11
pkg/encoding/mpv2/consentdata.go
Normal file
11
pkg/encoding/mpv2/consentdata.go
Normal file
@ -0,0 +1,11 @@
|
||||
package mpv2
|
||||
|
||||
type ConsentData struct {
|
||||
AdStorage *Consent `json:"ad_storage,omitempty"`
|
||||
AdUserData *Consent `json:"ad_user_data,omitempty"`
|
||||
AdPersonalization *Consent `json:"ad_personalization,omitempty"`
|
||||
AnalyticsStorage *Consent `json:"analytics_storage,omitempty"`
|
||||
FunctionalityStorage *Consent `json:"functionality_storage,omitempty"`
|
||||
PersonalizationStorage *Consent `json:"personalization_storage,omitempty"`
|
||||
SecurityStorage *Consent `json:"security_storage,omitempty"`
|
||||
}
|
||||
6
pkg/encoding/mpv2/doc.go
Normal file
6
pkg/encoding/mpv2/doc.go
Normal file
@ -0,0 +1,6 @@
|
||||
package mpv2
|
||||
|
||||
// Google Analytics (GA4) Measurement Protocol
|
||||
//
|
||||
// Reference: https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference
|
||||
// Changelog: https://developers.google.com/analytics/devguides/collection/protocol/ga4/changelog
|
||||
27
pkg/encoding/mpv2/payload.go
Normal file
27
pkg/encoding/mpv2/payload.go
Normal file
@ -0,0 +1,27 @@
|
||||
package mpv2
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/foomo/sesamy-go/pkg/sesamy"
|
||||
)
|
||||
|
||||
// https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference?client_type=gtag#payload_post_body
|
||||
type Payload[P any] struct {
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
UserID string `json:"user_id,omitempty"`
|
||||
TimestampMicros int64 `json:"timestamp_micros,omitempty"`
|
||||
UserProperties map[string]any `json:"user_properties,omitempty"`
|
||||
Consent *ConsentData `json:"consent,omitempty"`
|
||||
Events []sesamy.Event[P] `json:"events,omitempty"`
|
||||
UserData *UserData `json:"user_data,omitempty"`
|
||||
DebugMode bool `json:"debug_mode,omitempty"`
|
||||
SessionID string `json:"session_id,omitempty"`
|
||||
EngagementTimeMSec int64 `json:"engagement_time_msec,omitempty"`
|
||||
}
|
||||
|
||||
func NewPayload[P any]() *Payload[P] {
|
||||
return &Payload[P]{
|
||||
TimestampMicros: time.Now().UnixMicro(),
|
||||
}
|
||||
}
|
||||
44
pkg/encoding/mpv2/payload_test.go
Normal file
44
pkg/encoding/mpv2/payload_test.go
Normal file
@ -0,0 +1,44 @@
|
||||
package mpv2_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
|
||||
"github.com/foomo/sesamy-go/pkg/event"
|
||||
"github.com/foomo/sesamy-go/pkg/event/params"
|
||||
"github.com/foomo/sesamy-go/pkg/sesamy"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPayload(t *testing.T) {
|
||||
t.Parallel()
|
||||
v := mpv2.Payload[params.PageView]{
|
||||
ClientID: "C123456",
|
||||
UserID: "U123456",
|
||||
TimestampMicros: 1727701064057701,
|
||||
UserProperties: nil,
|
||||
Consent: nil,
|
||||
Events: []sesamy.Event[params.PageView]{
|
||||
event.NewPageView(params.PageView{
|
||||
PageTitle: "Home",
|
||||
PageLocation: "https://foomo.org",
|
||||
}),
|
||||
},
|
||||
UserData: nil,
|
||||
DebugMode: true,
|
||||
SessionID: "S123456",
|
||||
EngagementTimeMSec: 100,
|
||||
}
|
||||
|
||||
out, err := json.Marshal(v)
|
||||
require.NoError(t, err)
|
||||
expected := `{"debug_mode":true,"session_id":"S123456","engagement_time_msec":100,"client_id":"C123456","user_id":"U123456","timestamp_micros":1727701064057701,"events":[{"name":"page_view","params":{"page_title":"Home","page_location":"https://foomo.org"}}]}`
|
||||
assert.JSONEq(t, expected, string(out))
|
||||
|
||||
var in mpv2.Payload[params.PageView]
|
||||
err = json.Unmarshal(out, &in)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, v, in)
|
||||
}
|
||||
19
pkg/encoding/mpv2/sha256hash.go
Normal file
19
pkg/encoding/mpv2/sha256hash.go
Normal file
@ -0,0 +1,19 @@
|
||||
package mpv2
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SHA256Hash string
|
||||
|
||||
func (s SHA256Hash) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func NewSHA256Hash(s string) SHA256Hash {
|
||||
h := sha256.New()
|
||||
h.Write([]byte(strings.TrimSpace(strings.ToLower(s))))
|
||||
return SHA256Hash(hex.EncodeToString(h.Sum(nil)))
|
||||
}
|
||||
8
pkg/encoding/mpv2/userdata.go
Normal file
8
pkg/encoding/mpv2/userdata.go
Normal file
@ -0,0 +1,8 @@
|
||||
package mpv2
|
||||
|
||||
// UserData https://developers.google.com/analytics/devguides/collection/ga4/uid-data
|
||||
type UserData struct {
|
||||
SHA256EmailAddress SHA256Hash `json:"sha256_email_address,omitempty"`
|
||||
SHA256PhoneNumber SHA256Hash `json:"sha256_phone_number,omitempty"`
|
||||
Address UserDataAddress `json:"address,omitempty"`
|
||||
}
|
||||
12
pkg/encoding/mpv2/userdataaddress.go
Normal file
12
pkg/encoding/mpv2/userdataaddress.go
Normal file
@ -0,0 +1,12 @@
|
||||
package mpv2
|
||||
|
||||
// UserDataAddress https://developers.google.com/analytics/devguides/collection/ga4/uid-data
|
||||
type UserDataAddress struct {
|
||||
SHA256FirstName SHA256Hash `json:"sha256_first_name,omitempty"`
|
||||
SHA256LastName SHA256Hash `json:"sha256_last_name,omitempty"`
|
||||
SHA256Street SHA256Hash `json:"sha256_street,omitempty"`
|
||||
City string `json:"city,omitempty"`
|
||||
Region string `json:"region,omitempty"`
|
||||
PostalCode string `json:"postal_code,omitempty"`
|
||||
Country string `json:"country,omitempty"`
|
||||
}
|
||||
109
pkg/encoding/mpv2encode/gtag.go
Normal file
109
pkg/encoding/mpv2encode/gtag.go
Normal file
@ -0,0 +1,109 @@
|
||||
package mpv2encode
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func GTag[P any](source mpv2.Payload[P], target any) error {
|
||||
targetData := map[string]any{
|
||||
"client_id": source.ClientID,
|
||||
"user_id": source.UserID,
|
||||
}
|
||||
|
||||
{ // user_property
|
||||
targetUserProperty := map[string]any{}
|
||||
targetUserPropertyNumber := map[string]any{}
|
||||
for k, v := range source.UserProperties {
|
||||
if s, ok := v.(string); ok {
|
||||
if f, err := strconv.ParseFloat(s, 64); err == nil {
|
||||
targetUserPropertyNumber[k] = f
|
||||
} else {
|
||||
targetUserProperty[k] = v
|
||||
}
|
||||
} else {
|
||||
targetUserProperty[k] = fmt.Sprintf("%s", v)
|
||||
}
|
||||
}
|
||||
targetData["user_property"] = targetUserProperty
|
||||
targetData["user_property_number"] = targetUserPropertyNumber
|
||||
}
|
||||
|
||||
sourceData := map[string]any{}
|
||||
|
||||
out, err := json.Marshal(source.Events[0])
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal event")
|
||||
}
|
||||
if err = json.Unmarshal(out, &sourceData); err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshal source events")
|
||||
}
|
||||
|
||||
{ // ecommerce
|
||||
targetData["event_name"] = sourceData["name"]
|
||||
|
||||
if params, ok := sourceData["params"].(map[string]any); ok {
|
||||
targetData["document_title"] = params["page_title"]
|
||||
delete(params, "page_title")
|
||||
targetData["document_referrer"] = params["page_referrer"]
|
||||
delete(params, "page_referrer")
|
||||
targetData["document_location"] = params["page_location"]
|
||||
delete(params, "page_location")
|
||||
targetData["currency"] = params["currency"]
|
||||
delete(params, "currency")
|
||||
targetData["promotion_id"] = params["promotion_id"]
|
||||
delete(params, "promotion_id")
|
||||
targetData["promotion_name"] = params["promotion_name"]
|
||||
delete(params, "promotion_name")
|
||||
targetData["location_id"] = params["location_id"]
|
||||
delete(params, "location_id")
|
||||
targetData["is_conversion"] = params["is_conversion"]
|
||||
delete(params, "is_conversion")
|
||||
targetData["items"] = params["items"]
|
||||
delete(params, "items")
|
||||
{ // user_property
|
||||
targetEventProperty := map[string]any{}
|
||||
targetEventPropertyNumber := map[string]any{}
|
||||
for k, v := range params {
|
||||
switch t := v.(type) {
|
||||
case float64:
|
||||
targetEventPropertyNumber[k] = v
|
||||
case string:
|
||||
if f, err := strconv.ParseFloat(t, 64); err == nil {
|
||||
targetEventPropertyNumber[k] = f
|
||||
} else {
|
||||
targetEventProperty[k] = v
|
||||
}
|
||||
default:
|
||||
targetEventProperty[k] = fmt.Sprintf("%s", v)
|
||||
}
|
||||
}
|
||||
targetData["event_parameter"] = targetEventProperty
|
||||
targetData["event_parameter_number"] = targetEventPropertyNumber
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// encode gtag event to map
|
||||
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
WeaklyTypedInput: true,
|
||||
Squash: true,
|
||||
Result: target,
|
||||
TagName: "json",
|
||||
IgnoreUntaggedFields: true,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create event decoder")
|
||||
}
|
||||
|
||||
if err := dec.Decode(targetData); err != nil {
|
||||
return errors.Wrap(err, "failed to decode event")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
75
pkg/encoding/mpv2encode/gtag_test.go
Normal file
75
pkg/encoding/mpv2encode/gtag_test.go
Normal file
@ -0,0 +1,75 @@
|
||||
package mpv2encode_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/foomo/gostandards/iso4217"
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/gtagencode"
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
|
||||
"github.com/foomo/sesamy-go/pkg/encoding/mpv2encode"
|
||||
"github.com/foomo/sesamy-go/pkg/event/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSelectItem(t *testing.T) {
|
||||
t.Parallel()
|
||||
query := "v=2&tid=G-F9XM71K45T>m=45he45m0v9184715813z89184708445za200zb9184708445&_p=1716795486104&_dbg=1&gcd=13l3l3l2l1&npa=1&dma_cps=sypham&dma=1&cid=179294588.1715353601&ecid=2065234266&ul=en-us&sr=2056x1329&_fplc=0&ur=&uaa=arm&uab=64&uafvl=Chromium%3B124.0.6367.201%7CGoogle%2520Chrome%3B124.0.6367.201%7CNot-A.Brand%3B99.0.0.0&uamb=0&uam=&uap=macOS&uapv=14.4.1&uaw=0&are=1&frm=0&pscdl=noapi&sst.gcd=13l3l3l2l1&sst.tft=1716795486104&sst.ude=0&_s=18&cu=USD&sid=1716807360&sct=16&seg=1&dl=https%3A%2F%2Fsesamy.local.bestbytes.net%2F%3Fgtm_debug%3D1716795486020&dr=https%3A%2F%2Ftagassistant.google.com%2F&dt=Home&en=remove_from_cart&pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Merchandise%20Store~cpSUMMER_FUN~ds2.22~lp0~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20Products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr10.01~qt3&ep.enable_page_views=false&epn.value=30.03&_et=10086&tfd=9253808&richsstsse"
|
||||
values, err := url.ParseQuery(query)
|
||||
require.NoError(t, err)
|
||||
|
||||
var incoming gtag.Payload
|
||||
err = gtag.Decode(values, &incoming)
|
||||
require.NoError(t, err)
|
||||
|
||||
var intermediate *mpv2.Payload[params.RemoveFromCart[params.Item]]
|
||||
err = gtagencode.MPv2(incoming, &intermediate)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, iso4217.USD, intermediate.Events[0].Params.Currency)
|
||||
|
||||
intermediate.Events[0].Params.Currency = iso4217.EUR
|
||||
|
||||
{
|
||||
out, err := json.MarshalIndent(intermediate, "", " ")
|
||||
require.NoError(t, err)
|
||||
fmt.Println(string(out))
|
||||
}
|
||||
|
||||
var outgoing gtag.Payload
|
||||
err = mpv2encode.GTag(*intermediate, &outgoing)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, iso4217.EUR, gtag.Get(outgoing.Currency))
|
||||
}
|
||||
|
||||
func TestSelectItem_Pointer(t *testing.T) {
|
||||
t.Parallel()
|
||||
query := "v=2&tid=G-F9XM71K45T>m=45he45m0v9184715813z89184708445za200zb9184708445&_p=1716795486104&_dbg=1&gcd=13l3l3l2l1&npa=1&dma_cps=sypham&dma=1&cid=179294588.1715353601&ecid=2065234266&ul=en-us&sr=2056x1329&_fplc=0&ur=&uaa=arm&uab=64&uafvl=Chromium%3B124.0.6367.201%7CGoogle%2520Chrome%3B124.0.6367.201%7CNot-A.Brand%3B99.0.0.0&uamb=0&uam=&uap=macOS&uapv=14.4.1&uaw=0&are=1&frm=0&pscdl=noapi&sst.gcd=13l3l3l2l1&sst.tft=1716795486104&sst.ude=0&_s=18&cu=USD&sid=1716807360&sct=16&seg=1&dl=https%3A%2F%2Fsesamy.local.bestbytes.net%2F%3Fgtm_debug%3D1716795486020&dr=https%3A%2F%2Ftagassistant.google.com%2F&dt=Home&en=remove_from_cart&pr1=idSKU_12345~nmStan%20and%20Friends%20Tee~afGoogle%20Merchandise%20Store~cpSUMMER_FUN~ds2.22~lp0~brGoogle~caApparel~c2Adult~c3Shirts~c4Crew~c5Short%20sleeve~lirelated_products~lnRelated%20Products~vagreen~loChIJIQBpAG2ahYAR_6128GcTUEo~pr10.01~qt3&ep.enable_page_views=false&epn.value=30.03&_et=10086&tfd=9253808&richsstsse"
|
||||
values, err := url.ParseQuery(query)
|
||||
require.NoError(t, err)
|
||||
|
||||
var incoming *gtag.Payload
|
||||
err = gtag.Decode(values, &incoming)
|
||||
require.NoError(t, err)
|
||||
|
||||
var intermediate *mpv2.Payload[params.RemoveFromCart[params.Item]]
|
||||
err = gtagencode.MPv2(*incoming, &intermediate)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, iso4217.USD, intermediate.Events[0].Params.Currency)
|
||||
|
||||
// override value
|
||||
intermediate.Events[0].Params.Currency = iso4217.EUR
|
||||
|
||||
// {
|
||||
// out, err := json.MarshalIndent(intermediate, "", " ")
|
||||
// require.NoError(t, err)
|
||||
// fmt.Println(string(out))
|
||||
// }
|
||||
|
||||
err = mpv2encode.GTag(*intermediate, &incoming)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, iso4217.EUR, gtag.Get(incoming.Currency))
|
||||
}
|
||||
12
pkg/event/addpaymentinfo.go
Normal file
12
pkg/event/addpaymentinfo.go
Normal file
@ -0,0 +1,12 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"github.com/foomo/sesamy-go/pkg/event/params"
|
||||
"github.com/foomo/sesamy-go/pkg/sesamy"
|
||||
)
|
||||
|
||||
type AddPaymentInfo sesamy.Event[params.AddPaymentInfo[params.Item]]
|
||||
|
||||
func NewAddPaymentInfo(p params.AddPaymentInfo[params.Item]) sesamy.Event[params.AddPaymentInfo[params.Item]] {
|
||||
return sesamy.NewEvent(sesamy.EventNameAddPaymentInfo, p)
|
||||
}
|
||||
12
pkg/event/addshippinginfo.go
Normal file
12
pkg/event/addshippinginfo.go
Normal file
@ -0,0 +1,12 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"github.com/foomo/sesamy-go/pkg/event/params"
|
||||
"github.com/foomo/sesamy-go/pkg/sesamy"
|
||||
)
|
||||
|
||||
type AddShippingInfo sesamy.Event[params.AddShippingInfo[params.Item]]
|
||||
|
||||
func NewAddShippingInfo(p params.AddShippingInfo[params.Item]) sesamy.Event[params.AddShippingInfo[params.Item]] {
|
||||
return sesamy.NewEvent(sesamy.EventNameAddShippingInfo, p)
|
||||
}
|
||||
12
pkg/event/addtocart.go
Normal file
12
pkg/event/addtocart.go
Normal file
@ -0,0 +1,12 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"github.com/foomo/sesamy-go/pkg/event/params"
|
||||
"github.com/foomo/sesamy-go/pkg/sesamy"
|
||||
)
|
||||
|
||||
type AddToCart sesamy.Event[params.AddToCart[params.Item]]
|
||||
|
||||
func NewAddToCart(p params.AddToCart[params.Item]) sesamy.Event[params.AddToCart[params.Item]] {
|
||||
return sesamy.NewEvent(sesamy.EventNameAddToCart, p)
|
||||
}
|
||||
12
pkg/event/addtowishlist.go
Normal file
12
pkg/event/addtowishlist.go
Normal file
@ -0,0 +1,12 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"github.com/foomo/sesamy-go/pkg/event/params"
|
||||
"github.com/foomo/sesamy-go/pkg/sesamy"
|
||||
)
|
||||
|
||||
type AddToWishlist sesamy.Event[params.AddToWishlist[params.Item]]
|
||||
|
||||
func NewAddToWishlist(p params.AddToWishlist[params.Item]) sesamy.Event[params.AddToWishlist[params.Item]] {
|
||||
return sesamy.NewEvent(sesamy.EventNameAddToWishlist, p)
|
||||
}
|
||||
12
pkg/event/adimpression.go
Normal file
12
pkg/event/adimpression.go
Normal file
@ -0,0 +1,12 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"github.com/foomo/sesamy-go/pkg/event/params"
|
||||
"github.com/foomo/sesamy-go/pkg/sesamy"
|
||||
)
|
||||
|
||||
type AdImpression sesamy.Event[params.AdImpression]
|
||||
|
||||
func NewAdImpression(p params.AdImpression) sesamy.Event[params.AdImpression] {
|
||||
return sesamy.NewEvent(sesamy.EventNameAdImpression, p)
|
||||
}
|
||||
12
pkg/event/begincheckout.go
Normal file
12
pkg/event/begincheckout.go
Normal file
@ -0,0 +1,12 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"github.com/foomo/sesamy-go/pkg/event/params"
|
||||
"github.com/foomo/sesamy-go/pkg/sesamy"
|
||||
)
|
||||
|
||||
type BeginCheckout sesamy.Event[params.BeginCheckout[params.Item]]
|
||||
|
||||
func NewBeginCheckout(p params.BeginCheckout[params.Item]) sesamy.Event[params.BeginCheckout[params.Item]] {
|
||||
return sesamy.NewEvent(sesamy.EventNameBeginCheckout, p)
|
||||
}
|
||||
12
pkg/event/campaigndetails.go
Normal file
12
pkg/event/campaigndetails.go
Normal file
@ -0,0 +1,12 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"github.com/foomo/sesamy-go/pkg/event/params"
|
||||
"github.com/foomo/sesamy-go/pkg/sesamy"
|
||||
)
|
||||
|
||||
type CampaignDetails sesamy.Event[params.CampaignDetails]
|
||||
|
||||
func NewCampaignDetails(p params.CampaignDetails) sesamy.Event[params.CampaignDetails] {
|
||||
return sesamy.NewEvent(sesamy.EventNameCampaignDetails, p)
|
||||
}
|
||||
12
pkg/event/click.go
Normal file
12
pkg/event/click.go
Normal file
@ -0,0 +1,12 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"github.com/foomo/sesamy-go/pkg/event/params"
|
||||
"github.com/foomo/sesamy-go/pkg/sesamy"
|
||||
)
|
||||
|
||||
type Click sesamy.Event[params.Click]
|
||||
|
||||
func NewClick(p params.Click) sesamy.Event[params.Click] {
|
||||
return sesamy.NewEvent(sesamy.EventNameClick, p)
|
||||
}
|
||||
12
pkg/event/closeconvertlead.go
Normal file
12
pkg/event/closeconvertlead.go
Normal file
@ -0,0 +1,12 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"github.com/foomo/sesamy-go/pkg/event/params"
|
||||
"github.com/foomo/sesamy-go/pkg/sesamy"
|
||||
)
|
||||
|
||||
type CloseConvertLead sesamy.Event[params.CloseConvertLead[params.Item]]
|
||||
|
||||
func NewCloseConvertLead(p params.CloseConvertLead[params.Item]) sesamy.Event[params.CloseConvertLead[params.Item]] {
|
||||
return sesamy.NewEvent(sesamy.EventNameCloseConvertLead, p)
|
||||
}
|
||||
12
pkg/event/closeunconvertlead.go
Normal file
12
pkg/event/closeunconvertlead.go
Normal file
@ -0,0 +1,12 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"github.com/foomo/sesamy-go/pkg/event/params"
|
||||
"github.com/foomo/sesamy-go/pkg/sesamy"
|
||||
)
|
||||
|
||||
type CloseUnconvertLead sesamy.Event[params.CloseUnconvertLead[params.Item]]
|
||||
|
||||
func NewCloseUnconvertLead(p params.CloseUnconvertLead[params.Item]) sesamy.Event[params.CloseUnconvertLead[params.Item]] {
|
||||
return sesamy.NewEvent(sesamy.EventNameCloseUnconvertLead, p)
|
||||
}
|
||||
12
pkg/event/disqualifylead.go
Normal file
12
pkg/event/disqualifylead.go
Normal file
@ -0,0 +1,12 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"github.com/foomo/sesamy-go/pkg/event/params"
|
||||
"github.com/foomo/sesamy-go/pkg/sesamy"
|
||||
)
|
||||
|
||||
type DisqualifyLead sesamy.Event[params.DisqualifyLead[params.Item]]
|
||||
|
||||
func NewDisqualifyLead(p params.DisqualifyLead[params.Item]) sesamy.Event[params.DisqualifyLead[params.Item]] {
|
||||
return sesamy.NewEvent(sesamy.EventNameDisqualifyLead, p)
|
||||
}
|
||||
5
pkg/event/doc.go
Normal file
5
pkg/event/doc.go
Normal file
@ -0,0 +1,5 @@
|
||||
package event
|
||||
|
||||
// Google Analytics (GA4) Measurement Protocol events
|
||||
//
|
||||
// Reference: https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference/events
|
||||
12
pkg/event/earnvirtualmoney.go
Normal file
12
pkg/event/earnvirtualmoney.go
Normal file
@ -0,0 +1,12 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"github.com/foomo/sesamy-go/pkg/event/params"
|
||||
"github.com/foomo/sesamy-go/pkg/sesamy"
|
||||
)
|
||||
|
||||
type EarnVirtualMoney sesamy.Event[params.EarnVirtualMoney]
|
||||
|
||||
func NewEarnVirtualMoney(p params.EarnVirtualMoney) sesamy.Event[params.EarnVirtualMoney] {
|
||||
return sesamy.NewEvent(sesamy.EventNameEarnVirtualMoney, p)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user