mirror of
https://github.com/foomo/sesamy-go.git
synced 2025-10-16 12:35:43 +00:00
initial commit
This commit is contained in:
commit
d80faf60d8
12
.editorconfig
Normal file
12
.editorconfig
Normal file
@ -0,0 +1,12 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = tab
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{yaml,yml,md,mdx}]
|
||||
indent_style = space
|
||||
15
.github/dependabot.yml
vendored
Normal file
15
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
# 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: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
31
.github/workflows/release.yml
vendored
Normal file
31
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
name: Release Tag
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*.*.*
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- run: git fetch --force --tags
|
||||
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 'stable'
|
||||
|
||||
- uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
version: latest
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
37
.github/workflows/test.yml
vendored
Normal file
37
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
name: Test Branch
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 'stable'
|
||||
|
||||
- uses: gotesttools/gotestfmt-action@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout=5m
|
||||
|
||||
- run: make test
|
||||
|
||||
- uses: coverallsapp/github-action@v2
|
||||
with:
|
||||
file: coverage.out
|
||||
|
||||
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
.*
|
||||
*.log
|
||||
*.out
|
||||
!.github/
|
||||
!.husky/
|
||||
!.editorconfig
|
||||
!.gitignore
|
||||
!.golangci.yml
|
||||
!.goreleaser.yml
|
||||
!.husky.yaml
|
||||
/tmp/
|
||||
89
.golangci.yml
Normal file
89
.golangci.yml
Normal file
@ -0,0 +1,89 @@
|
||||
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
|
||||
|
||||
linters:
|
||||
enable:
|
||||
# Enabled by default linters:
|
||||
- errcheck
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
|
||||
# Disabled by default linters:
|
||||
- asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers [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]
|
||||
- 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]
|
||||
- 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]
|
||||
- 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]
|
||||
- importas # Enforces consistent import aliases [fast: false, auto-fix: false]
|
||||
#- maintidx # maintidx measures the maintainability index of each function. [fast: true, 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]
|
||||
- nilerr # Finds the code that returns nil even if it checks that the error is not nil. [fast: false, auto-fix: false]
|
||||
- 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]
|
||||
- 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]
|
||||
- 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]
|
||||
- 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]
|
||||
- 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]
|
||||
- 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
|
||||
5
.goreleaser.yml
Normal file
5
.goreleaser.yml
Normal file
@ -0,0 +1,5 @@
|
||||
builds:
|
||||
- skip: true
|
||||
|
||||
changelog:
|
||||
use: github-native
|
||||
15
.husky.yaml
Normal file
15
.husky.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
hooks:
|
||||
pre-commit:
|
||||
- golangci-lint run --fast
|
||||
- husky lint-staged
|
||||
commit-msg:
|
||||
# only execute if not in a merge
|
||||
- if [[ -z $(git rev-parse -q --verify MERGE_HEAD) ]]; then husky lint-commit; fi
|
||||
|
||||
lint-staged:
|
||||
'*.go':
|
||||
- goimports -l -w
|
||||
|
||||
lint-commit:
|
||||
types: '^(feat|fix|build|chore|docs|perf|refactor|revert|style|test|wip)$'
|
||||
header: '^(?P<type>\w+)(\((?P<scope>[\w/.-]+)\))?(?P<breaking>!)?:( +)?(?P<header>.+)'
|
||||
3
.husky/applypatch-msg
Executable file
3
.husky/applypatch-msg
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/commit-msg
Executable file
3
.husky/commit-msg
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/fsmonitor-watchman
Executable file
3
.husky/fsmonitor-watchman
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/post-update
Executable file
3
.husky/post-update
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/pre-applypatch
Executable file
3
.husky/pre-applypatch
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/pre-commit
Executable file
3
.husky/pre-commit
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/pre-merge-commit
Executable file
3
.husky/pre-merge-commit
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/pre-push
Executable file
3
.husky/pre-push
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/pre-rebase
Executable file
3
.husky/pre-rebase
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/pre-receive
Executable file
3
.husky/pre-receive
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/prepare-commit-msg
Executable file
3
.husky/prepare-commit-msg
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/push-to-checkout
Executable file
3
.husky/push-to-checkout
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/sendemail-validate
Executable file
3
.husky/sendemail-validate
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/update
Executable file
3
.husky/update
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
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.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* 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
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* 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
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
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,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
info@bestbytes.de.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**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.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
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
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**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.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
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.
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Foomo web framework
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
84
Makefile
Normal file
84
Makefile
Normal file
@ -0,0 +1,84 @@
|
||||
.DEFAULT_GOAL:=help
|
||||
-include .makerc
|
||||
|
||||
# --- Targets -----------------------------------------------------------------
|
||||
|
||||
# This allows us to accept extra arguments
|
||||
%: .husky
|
||||
@:
|
||||
|
||||
.PHONY: .husky
|
||||
# Configure git hooks for husky
|
||||
.husky:
|
||||
@if ! command -v husky &> /dev/null; then \
|
||||
echo "ERROR: missing executeable 'husky', please run:"; \
|
||||
echo "\n$ go install github.com/go-courier/husky/cmd/husky@latest\n"; \
|
||||
fi
|
||||
@git config core.hooksPath .husky
|
||||
|
||||
## === Tasks ===
|
||||
|
||||
.PHONY: doc
|
||||
## Run tests
|
||||
doc:
|
||||
@open "http://localhost:6060/pkg/github.com/foomo/keel/"
|
||||
@godoc -http=localhost:6060 -play
|
||||
|
||||
.PHONY: test
|
||||
## Run tests
|
||||
test:
|
||||
@go test -coverprofile=coverage.out -race -json ./... | gotestfmt
|
||||
|
||||
.PHONY: lint
|
||||
## Run linter
|
||||
lint:
|
||||
@golangci-lint run
|
||||
|
||||
.PHONY: lint.fix
|
||||
## Fix lint violations
|
||||
lint.fix:
|
||||
@golangci-lint run --fix
|
||||
|
||||
.PHONY: tidy
|
||||
## Run go mod tidy
|
||||
tidy:
|
||||
@go mod tidy
|
||||
|
||||
.PHONY: outdated
|
||||
## Show outdated direct dependencies
|
||||
outdated:
|
||||
@go list -u -m -json all | go-mod-outdated -update -direct
|
||||
|
||||
## === Utils ===
|
||||
|
||||
## Show help text
|
||||
help:
|
||||
@awk '{ \
|
||||
if ($$0 ~ /^.PHONY: [a-zA-Z\-\_0-9]+$$/) { \
|
||||
helpCommand = substr($$0, index($$0, ":") + 2); \
|
||||
if (helpMessage) { \
|
||||
printf "\033[36m%-23s\033[0m %s\n", \
|
||||
helpCommand, helpMessage; \
|
||||
helpMessage = ""; \
|
||||
} \
|
||||
} else if ($$0 ~ /^[a-zA-Z\-\_0-9.]+:/) { \
|
||||
helpCommand = substr($$0, 0, index($$0, ":")); \
|
||||
if (helpMessage) { \
|
||||
printf "\033[36m%-23s\033[0m %s\n", \
|
||||
helpCommand, helpMessage"\n"; \
|
||||
helpMessage = ""; \
|
||||
} \
|
||||
} else if ($$0 ~ /^##/) { \
|
||||
if (helpMessage) { \
|
||||
helpMessage = helpMessage"\n "substr($$0, 3); \
|
||||
} else { \
|
||||
helpMessage = substr($$0, 3); \
|
||||
} \
|
||||
} else { \
|
||||
if (helpMessage) { \
|
||||
print "\n "helpMessage"\n" \
|
||||
} \
|
||||
helpMessage = ""; \
|
||||
} \
|
||||
}' \
|
||||
$(MAKEFILE_LIST)
|
||||
15
README.md
Normal file
15
README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Sesamy
|
||||
|
||||
[](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)
|
||||
|
||||
> **Se**rver **S**ide T**a**g **M**anagement **S**ystem
|
||||
|
||||
## How to Contribute
|
||||
|
||||
Make a pull request...
|
||||
|
||||
## License
|
||||
|
||||
Distributed under MIT License, please see license file within the code for more details.
|
||||
35
go.mod
Normal file
35
go.mod
Normal file
@ -0,0 +1,35 @@
|
||||
module github.com/foomo/sesamy
|
||||
|
||||
go 1.22.0
|
||||
|
||||
require (
|
||||
github.com/ThreeDotsLabs/watermill v1.3.5
|
||||
github.com/foomo/keel v0.17.3
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
go.uber.org/zap v1.27.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
|
||||
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
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
102
go.sum
Normal file
102
go.sum
Normal file
@ -0,0 +1,102 @@
|
||||
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=
|
||||
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/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/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/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-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/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/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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
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.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=
|
||||
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/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
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/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/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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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/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.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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
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/xerrors v0.0.0-20190717185122-a985d3407aa7/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=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=
|
||||
6
integration/watermill/gtm/provider.go
Normal file
6
integration/watermill/gtm/provider.go
Normal file
@ -0,0 +1,6 @@
|
||||
package gtm
|
||||
|
||||
const (
|
||||
HeaderUUID = "Message-Uuid"
|
||||
ProviderName = "gtm"
|
||||
)
|
||||
179
integration/watermill/gtm/publisher.go
Normal file
179
integration/watermill/gtm/publisher.go
Normal file
@ -0,0 +1,179 @@
|
||||
package gtm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/ThreeDotsLabs/watermill"
|
||||
"github.com/ThreeDotsLabs/watermill/message"
|
||||
mpv2 "github.com/foomo/sesamy/measurementprotocol/v2"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrErrorResponse = errors.New("server responded with error status")
|
||||
ErrPublisherClosed = errors.New("publisher is closed")
|
||||
)
|
||||
|
||||
type (
|
||||
Publisher struct {
|
||||
//l watermill.LoggerAdapter
|
||||
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.Marshal(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u := p.url + "?" + values.Encode() + "&richsstsse"
|
||||
|
||||
req, err := http.NewRequestWithContext(msg.Context(), "POST", u, body)
|
||||
|
||||
for s, s2 := range msg.Metadata {
|
||||
req.Header.Set(s, s2)
|
||||
}
|
||||
|
||||
//req, err := p.marshalMessageFunc(topic, msg)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cannot marshal message %s", msg.UUID)
|
||||
}
|
||||
|
||||
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, "publishing message %s failed", msg.UUID)
|
||||
}
|
||||
|
||||
if err = p.handleResponseBody(resp, logFields); 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, logFields watermill.LogFields) 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)
|
||||
|
||||
// DefaultMarshalMessageFunc transforms the message into a HTTP POST request.
|
||||
// It encodes the UUID and Metadata in request headers.
|
||||
func DefaultMarshalMessageFunc(url string, msg *message.Message) (*http.Request, error) {
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(msg.Payload))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set(HeaderUUID, msg.UUID)
|
||||
|
||||
return req, nil
|
||||
}
|
||||
6
integration/watermill/keel/provider.go
Normal file
6
integration/watermill/keel/provider.go
Normal file
@ -0,0 +1,6 @@
|
||||
package keel
|
||||
|
||||
const (
|
||||
ProviderName = "keel"
|
||||
MetadataEventName = "X-Event-Name"
|
||||
)
|
||||
215
integration/watermill/keel/subscriber.go
Normal file
215
integration/watermill/keel/subscriber.go
Normal file
@ -0,0 +1,215 @@
|
||||
package keel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/ThreeDotsLabs/watermill"
|
||||
"github.com/ThreeDotsLabs/watermill/message"
|
||||
"github.com/foomo/keel/net/http/log"
|
||||
keelhttputils "github.com/foomo/keel/utils/net/http"
|
||||
mp "github.com/foomo/sesamy/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy/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 := log.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
|
||||
|
||||
// local logger
|
||||
l := s.l
|
||||
|
||||
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(l, w, r, http.StatusMethodNotAllowed, errors.New(http.StatusText(http.StatusMethodNotAllowed)))
|
||||
return
|
||||
}
|
||||
|
||||
// unmarshal event
|
||||
var event *mpv2.Event
|
||||
if err := mpv2.UnmarshalURLValues(values, &event); err != nil {
|
||||
keelhttputils.InternalServerError(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(l, r, event); err != nil {
|
||||
keelhttputils.InternalServerError(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)
|
||||
if labeler, ok := log.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, ","))
|
||||
}
|
||||
|
||||
// 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.Info("message acked")
|
||||
return nil
|
||||
case <-msg.Nacked():
|
||||
l.Info("message nacked")
|
||||
return ErrMessageNacked
|
||||
case <-r.Context().Done():
|
||||
l.Info("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
|
||||
}
|
||||
90
measurementprotocol/getset.go
Normal file
90
measurementprotocol/getset.go
Normal file
@ -0,0 +1,90 @@
|
||||
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 Get[T any](v *T) T {
|
||||
return *v
|
||||
}
|
||||
|
||||
func GetDefault[T any](v *T, fallback T) T {
|
||||
if v == nil {
|
||||
return fallback
|
||||
}
|
||||
return *v
|
||||
}
|
||||
93
measurementprotocol/v2/client.go
Normal file
93
measurementprotocol/v2/client.go
Normal file
@ -0,0 +1,93 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
Client struct {
|
||||
l *zap.Logger
|
||||
url string
|
||||
httpClient *http.Client
|
||||
}
|
||||
ClientOption func(*Client)
|
||||
)
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Options
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func ClientWithHTTPClient(v *http.Client) ClientOption {
|
||||
return func(o *Client) {
|
||||
o.httpClient = v
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Constructor
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func NewClient(l *zap.Logger, url string, opts ...ClientOption) *Client {
|
||||
inst := &Client{
|
||||
l: l,
|
||||
url: url,
|
||||
httpClient: http.DefaultClient,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(inst)
|
||||
}
|
||||
return inst
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Getter
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func (c *Client) HTTPClient() *http.Client {
|
||||
return c.httpClient
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Public methods
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func (c *Client) Send(ctx context.Context, event *Event) error {
|
||||
|
||||
values, body, err := Marshal(event)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshall event")
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
c.url+"?"+values.Encode(),
|
||||
body,
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create request")
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to send request")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
265
measurementprotocol/v2/event.go
Normal file
265
measurementprotocol/v2/event.go
Normal file
@ -0,0 +1,265 @@
|
||||
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 interace 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 comming 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"`
|
||||
}
|
||||
|
||||
|
||||
66
measurementprotocol/v2/event/addpaymentinfo.go
Normal file
66
measurementprotocol/v2/event/addpaymentinfo.go
Normal file
@ -0,0 +1,66 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy/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
|
||||
Value float64
|
||||
Coupon string
|
||||
PaymentType string
|
||||
Items []*Item
|
||||
}
|
||||
|
||||
func (e *AddPaymentInfo) MPv2() *mpv2.Event {
|
||||
items := make([]mpv2.Item, len(e.Items))
|
||||
for i, item := range e.Items {
|
||||
items[i] = *item.MPv2()
|
||||
}
|
||||
return &mpv2.Event{
|
||||
Currency: mp.SetString(e.Currency),
|
||||
EventParameter: map[string]string{
|
||||
mpv2.EventParameterCoupon.String(): e.Coupon,
|
||||
mpv2.EventParameterPaymentType.String(): e.PaymentType,
|
||||
},
|
||||
EventParameterNumber: map[string]string{
|
||||
mpv2.EventParameterNumberValue.String(): *mp.SetFloat64(e.Value),
|
||||
},
|
||||
Items: items,
|
||||
}
|
||||
}
|
||||
66
measurementprotocol/v2/event/addshippinginfo.go
Normal file
66
measurementprotocol/v2/event/addshippinginfo.go
Normal file
@ -0,0 +1,66 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy/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
|
||||
Value float64
|
||||
Coupon string
|
||||
ShippingTier string
|
||||
Items []*Item
|
||||
}
|
||||
|
||||
func (e *AddShippingInfo) MPv2() *mpv2.Event {
|
||||
items := make([]mpv2.Item, len(e.Items))
|
||||
for i, item := range e.Items {
|
||||
items[i] = *item.MPv2()
|
||||
}
|
||||
return &mpv2.Event{
|
||||
Currency: mp.SetString(e.Currency),
|
||||
EventParameter: map[string]string{
|
||||
mpv2.EventParameterCoupon.String(): e.Coupon,
|
||||
mpv2.EventParameterShippingTier.String(): e.ShippingTier,
|
||||
},
|
||||
EventParameterNumber: map[string]string{
|
||||
mpv2.EventParameterNumberValue.String(): *mp.SetFloat64(e.Value),
|
||||
},
|
||||
Items: items,
|
||||
}
|
||||
}
|
||||
58
measurementprotocol/v2/event/addtocart.go
Normal file
58
measurementprotocol/v2/event/addtocart.go
Normal file
@ -0,0 +1,58 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy/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
|
||||
Value float64
|
||||
Items []*Item
|
||||
}
|
||||
|
||||
func (e *AddToCart) MPv2() *mpv2.Event {
|
||||
items := make([]mpv2.Item, len(e.Items))
|
||||
for i, item := range e.Items {
|
||||
items[i] = *item.MPv2()
|
||||
}
|
||||
return &mpv2.Event{
|
||||
Currency: mp.SetString(e.Currency),
|
||||
EventParameterNumber: map[string]string{
|
||||
mpv2.EventParameterNumberValue.String(): *mp.SetFloat64(e.Value),
|
||||
},
|
||||
Items: items,
|
||||
}
|
||||
}
|
||||
58
measurementprotocol/v2/event/addtowishlist.go
Normal file
58
measurementprotocol/v2/event/addtowishlist.go
Normal file
@ -0,0 +1,58 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy/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
|
||||
Value float64
|
||||
Items []*Item
|
||||
}
|
||||
|
||||
func (e *AddToWishlist) MPv2() *mpv2.Event {
|
||||
items := make([]mpv2.Item, len(e.Items))
|
||||
for i, item := range e.Items {
|
||||
items[i] = *item.MPv2()
|
||||
}
|
||||
return &mpv2.Event{
|
||||
Currency: mp.SetString(e.Currency),
|
||||
EventParameterNumber: map[string]string{
|
||||
mpv2.EventParameterNumberValue.String(): *mp.SetFloat64(e.Value),
|
||||
},
|
||||
Items: items,
|
||||
}
|
||||
}
|
||||
63
measurementprotocol/v2/event/begincheckout.go
Normal file
63
measurementprotocol/v2/event/begincheckout.go
Normal file
@ -0,0 +1,63 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy/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
|
||||
Value float64
|
||||
Coupon string
|
||||
Items []*Item
|
||||
}
|
||||
|
||||
func (e *BeginCheckout) MPv2() *mpv2.Event {
|
||||
items := make([]mpv2.Item, len(e.Items))
|
||||
for i, item := range e.Items {
|
||||
items[i] = *item.MPv2()
|
||||
}
|
||||
return &mpv2.Event{
|
||||
Currency: mp.SetString(e.Currency),
|
||||
EventParameter: map[string]string{
|
||||
mpv2.EventParameterCoupon.String(): e.Coupon,
|
||||
},
|
||||
EventParameterNumber: map[string]string{
|
||||
mpv2.EventParameterNumberValue.String(): *mp.SetFloat64(e.Value),
|
||||
},
|
||||
Items: items,
|
||||
}
|
||||
}
|
||||
50
measurementprotocol/v2/event/item.go
Normal file
50
measurementprotocol/v2/event/item.go
Normal file
@ -0,0 +1,50 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy/measurementprotocol/v2"
|
||||
)
|
||||
|
||||
type Item struct {
|
||||
ID string
|
||||
Name string
|
||||
Brand string
|
||||
CategoryHierarchy1 string
|
||||
CategoryHierarchy2 string
|
||||
CategoryHierarchy3 string
|
||||
CategoryHierarchy4 string
|
||||
CategoryHierarchy5 string
|
||||
Price string
|
||||
Quantity string
|
||||
Variant string
|
||||
Coupon string
|
||||
Discount float64
|
||||
Index int
|
||||
ListName string
|
||||
ListID string
|
||||
Affiliation string
|
||||
LocationID string
|
||||
}
|
||||
|
||||
func (e *Item) MPv2() *mpv2.Item {
|
||||
return &mpv2.Item{
|
||||
ID: mp.SetString(e.ID),
|
||||
Name: mp.SetString(e.Name),
|
||||
Brand: mp.SetString(e.Brand),
|
||||
CategoryHierarchy1: mp.SetString(e.CategoryHierarchy1),
|
||||
CategoryHierarchy2: mp.SetString(e.CategoryHierarchy2),
|
||||
CategoryHierarchy3: mp.SetString(e.CategoryHierarchy3),
|
||||
CategoryHierarchy4: mp.SetString(e.CategoryHierarchy4),
|
||||
CategoryHierarchy5: mp.SetString(e.CategoryHierarchy5),
|
||||
Price: mp.SetString(e.Price),
|
||||
Quantity: mp.SetString(e.Quantity),
|
||||
Variant: mp.SetString(e.Variant),
|
||||
Coupon: mp.SetString(e.Coupon),
|
||||
Discount: mp.SetFloat64(e.Discount),
|
||||
ListName: mp.SetString(e.ListName),
|
||||
ListID: mp.SetString(e.ListID),
|
||||
ListPosition: mp.SetInt(e.Index),
|
||||
Affiliation: mp.SetString(e.Affiliation),
|
||||
LocationID: mp.SetString(e.LocationID),
|
||||
}
|
||||
}
|
||||
27
measurementprotocol/v2/event/login.go
Normal file
27
measurementprotocol/v2/event/login.go
Normal file
@ -0,0 +1,27 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy/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
|
||||
}
|
||||
|
||||
func (e *Login) MPv2() *mpv2.Event {
|
||||
return &mpv2.Event{
|
||||
EventParameter: map[string]string{
|
||||
mpv2.EventParameterMethod.String(): *mp.SetString(e.Method),
|
||||
},
|
||||
}
|
||||
}
|
||||
72
measurementprotocol/v2/event/purchase.go
Normal file
72
measurementprotocol/v2/event/purchase.go
Normal file
@ -0,0 +1,72 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy/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
|
||||
Value float64
|
||||
TransactionID string
|
||||
Coupon string
|
||||
Shipping float64
|
||||
Tax float64
|
||||
Items []Item
|
||||
}
|
||||
|
||||
func (e *Purchase) MPv2() *mpv2.Event {
|
||||
items := make([]mpv2.Item, len(e.Items))
|
||||
for i, item := range e.Items {
|
||||
items[i] = *item.MPv2()
|
||||
}
|
||||
return &mpv2.Event{
|
||||
Currency: mp.SetString(e.Currency),
|
||||
EventParameter: map[string]string{
|
||||
mpv2.EventParameterTransactionID.String(): *mp.SetFloat64(e.Value),
|
||||
mpv2.EventParameterCoupon.String(): *mp.SetFloat64(e.Value),
|
||||
},
|
||||
EventParameterNumber: map[string]string{
|
||||
mpv2.EventParameterNumberValue.String(): *mp.SetFloat64(e.Value),
|
||||
mpv2.EventParameterNumberShipping.String(): *mp.SetFloat64(e.Value),
|
||||
mpv2.EventParameterNumberTax.String(): *mp.SetFloat64(e.Value),
|
||||
},
|
||||
Items: items,
|
||||
}
|
||||
}
|
||||
72
measurementprotocol/v2/event/refund.go
Normal file
72
measurementprotocol/v2/event/refund.go
Normal file
@ -0,0 +1,72 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy/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
|
||||
Value float64
|
||||
TransactionID string
|
||||
Coupon string
|
||||
Shipping float64
|
||||
Tax float64
|
||||
Items []Item
|
||||
}
|
||||
|
||||
func (e *Refund) MPv2() *mpv2.Event {
|
||||
items := make([]mpv2.Item, len(e.Items))
|
||||
for i, item := range e.Items {
|
||||
items[i] = *item.MPv2()
|
||||
}
|
||||
return &mpv2.Event{
|
||||
Currency: mp.SetString(e.Currency),
|
||||
EventParameter: map[string]string{
|
||||
mpv2.EventParameterTransactionID.String(): *mp.SetFloat64(e.Value),
|
||||
mpv2.EventParameterCoupon.String(): *mp.SetFloat64(e.Value),
|
||||
},
|
||||
EventParameterNumber: map[string]string{
|
||||
mpv2.EventParameterNumberValue.String(): *mp.SetFloat64(e.Value),
|
||||
mpv2.EventParameterNumberShipping.String(): *mp.SetFloat64(e.Value),
|
||||
mpv2.EventParameterNumberTax.String(): *mp.SetFloat64(e.Value),
|
||||
},
|
||||
Items: items,
|
||||
}
|
||||
}
|
||||
58
measurementprotocol/v2/event/removefromcart.go
Normal file
58
measurementprotocol/v2/event/removefromcart.go
Normal file
@ -0,0 +1,58 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy/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
|
||||
Value float64
|
||||
Items []*Item
|
||||
}
|
||||
|
||||
func (e *RemoveFromCart) MPv2() *mpv2.Event {
|
||||
items := make([]mpv2.Item, len(e.Items))
|
||||
for i, item := range e.Items {
|
||||
items[i] = *item.MPv2()
|
||||
}
|
||||
return &mpv2.Event{
|
||||
Currency: mp.SetString(e.Currency),
|
||||
EventParameterNumber: map[string]string{
|
||||
mpv2.EventParameterNumberValue.String(): *mp.SetFloat64(e.Value),
|
||||
},
|
||||
Items: items,
|
||||
}
|
||||
}
|
||||
27
measurementprotocol/v2/event/search.go
Normal file
27
measurementprotocol/v2/event/search.go
Normal file
@ -0,0 +1,27 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy/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
|
||||
}
|
||||
|
||||
func (e *Search) MPv2() *mpv2.Event {
|
||||
return &mpv2.Event{
|
||||
EventParameter: map[string]string{
|
||||
mpv2.EventParameterSearchTerm.String(): *mp.SetString(e.SearchTerm),
|
||||
},
|
||||
}
|
||||
}
|
||||
27
measurementprotocol/v2/event/signup.go
Normal file
27
measurementprotocol/v2/event/signup.go
Normal file
@ -0,0 +1,27 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
mp "github.com/foomo/sesamy/measurementprotocol"
|
||||
mpv2 "github.com/foomo/sesamy/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
|
||||
}
|
||||
|
||||
func (e *SignUp) MPv2() *mpv2.Event {
|
||||
return &mpv2.Event{
|
||||
EventParameter: map[string]string{
|
||||
mpv2.EventParameterMethod.String(): *mp.SetString(e.Method),
|
||||
},
|
||||
}
|
||||
}
|
||||
39
measurementprotocol/v2/eventname.go
Normal file
39
measurementprotocol/v2/eventname.go
Normal file
@ -0,0 +1,39 @@
|
||||
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)
|
||||
}
|
||||
17
measurementprotocol/v2/eventparameter.go
Normal file
17
measurementprotocol/v2/eventparameter.go
Normal file
@ -0,0 +1,17 @@
|
||||
package v2
|
||||
|
||||
type EventParameter string
|
||||
|
||||
const (
|
||||
EventParameterMethod EventParameter = "method"
|
||||
EventParameterCoupon EventParameter = "coupon"
|
||||
EventParameterPaymentType EventParameter = "payment_type"
|
||||
EventParameterShippingTier EventParameter = "shipping_tier"
|
||||
EventParameterTransactionID EventParameter = "transaction_id"
|
||||
EventParameterSearchTerm EventParameter = "search_term"
|
||||
)
|
||||
|
||||
func (s EventParameter) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
13
measurementprotocol/v2/eventparamternumber.go
Normal file
13
measurementprotocol/v2/eventparamternumber.go
Normal file
@ -0,0 +1,13 @@
|
||||
package v2
|
||||
|
||||
type EventParameterNumber string
|
||||
|
||||
const (
|
||||
EventParameterNumberValue EventParameterNumber = "value"
|
||||
EventParameterNumberShipping EventParameterNumber = "shipping"
|
||||
EventParameterNumberTax EventParameterNumber = "tax"
|
||||
)
|
||||
|
||||
func (s EventParameterNumber) String() string {
|
||||
return string(s)
|
||||
}
|
||||
45
measurementprotocol/v2/item.go
Normal file
45
measurementprotocol/v2/item.go
Normal file
@ -0,0 +1,45 @@
|
||||
package v2
|
||||
/**
|
||||
promotion_id: "pi",
|
||||
promotion_name: "pn",
|
||||
creative_name: "cn",
|
||||
creative_slot: "cs",
|
||||
*/
|
||||
type Item struct {
|
||||
// Exmaple: 12345
|
||||
ID *string `json:"id,omitempty" mapstructure:"id,omitempty"`
|
||||
// Example: Stan and Friends Tee
|
||||
Name *string `json:"nm,omitempty" mapstructure:"nm,omitempty"`
|
||||
// Exmaple: Google
|
||||
Brand *string `json:"br,omitempty" mapstructure:"br,omitempty"`
|
||||
// Exmaple: men
|
||||
CategoryHierarchy1 *string `json:"ca,omitempty" mapstructure:"ca,omitempty"`
|
||||
// Exmaple: t-shirts
|
||||
CategoryHierarchy2 *string `json:"c2,omitempty" mapstructure:"c2,omitempty"`
|
||||
// Exmaple: men
|
||||
CategoryHierarchy3 *string `json:"c3,omitempty" mapstructure:"c3,omitempty"`
|
||||
// Exmaple: men
|
||||
CategoryHierarchy4 *string `json:"c4,omitempty" mapstructure:"c4,omitempty"`
|
||||
// Exmaple: men
|
||||
CategoryHierarchy5 *string `json:"c5,omitempty" mapstructure:"c5,omitempty"`
|
||||
// Exmaple: Yellow
|
||||
Variant *string `json:"va,omitempty" mapstructure:"va,omitempty"`
|
||||
// Exmaple: 123.45
|
||||
Price *string `json:"pr,omitempty" mapstructure:"pr,omitempty"`
|
||||
// Exmaple: 1
|
||||
Quantity *string `json:"qt,omitempty" mapstructure:"qt,omitempty"`
|
||||
// Exmaple: 50%OFF
|
||||
Coupon *string `json:"cp,omitempty" mapstructure:"cp,omitempty"`
|
||||
// Exmaple: cross-selling: mens
|
||||
ListName *string `json:"ln,omitempty" mapstructure:"ln,omitempty"`
|
||||
// Exmaple: 10
|
||||
ListPosition *string `json:"lp,omitempty" mapstructure:"lp,omitempty"`
|
||||
// Exmaple: id-mens-123
|
||||
ListID *string `json:"li,omitempty" mapstructure:"li,omitempty"`
|
||||
// Exmaple: 10.00
|
||||
Discount *string `json:"ds,omitempty" mapstructure:"ds,omitempty"`
|
||||
// Exmaple: Foo Marketplace
|
||||
Affiliation *string `json:"af,omitempty" mapstructure:"af,omitempty"`
|
||||
// Example: ChIJIQBpAG2ahYAR_6128GcTUEo
|
||||
LocationID *string `json:"lo,omitempty" mapstructure:"lo,omitempty"`
|
||||
}
|
||||
29
measurementprotocol/v2/messagehandler.go
Normal file
29
measurementprotocol/v2/messagehandler.go
Normal file
@ -0,0 +1,29 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ThreeDotsLabs/watermill/message"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func MessageEventHandler(eventHandler func(event *Event, msg *message.Message) error) func(msg *message.Message) ([]*message.Message, error) {
|
||||
return func(msg *message.Message) ([]*message.Message, error) {
|
||||
var event *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
|
||||
}
|
||||
}
|
||||
20
measurementprotocol/v2/sst.go
Normal file
20
measurementprotocol/v2/sst.go
Normal file
@ -0,0 +1,20 @@
|
||||
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"`
|
||||
}
|
||||
188
measurementprotocol/v2/unmarshal.go
Normal file
188
measurementprotocol/v2/unmarshal.go
Normal file
@ -0,0 +1,188 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
RegexProduct = regexp.MustCompile(`pr([1-9]|[1-9][0-9]|1[0-9]{2}|200)`)
|
||||
)
|
||||
|
||||
const (
|
||||
ParameterItem = "pr"
|
||||
)
|
||||
|
||||
type Data map[string]any
|
||||
|
||||
func Marshal(input *Event) (url.Values, io.Reader, error) {
|
||||
|
||||
a, err := json.Marshal(input)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
data := Data{}
|
||||
if err := json.Unmarshal(a, &data); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to decode into map")
|
||||
}
|
||||
|
||||
ret := url.Values{}
|
||||
for k, v := range data {
|
||||
switch t := v.(type) {
|
||||
case []interface{}:
|
||||
for key, value := range t {
|
||||
switch tt := value.(type) {
|
||||
case map[string]interface{}:
|
||||
ret[fmt.Sprintf("%s%d", k, key+1)] = []string{EncodeObjectValue(tt)}
|
||||
default:
|
||||
panic("unhandled")
|
||||
}
|
||||
}
|
||||
case map[string]string:
|
||||
for key, value := range t {
|
||||
ret[fmt.Sprintf("%s.%s", k, key)] = []string{value}
|
||||
}
|
||||
case map[string]interface{}:
|
||||
for key, value := range t {
|
||||
ret[fmt.Sprintf("%s.%s", k, key)] = []string{fmt.Sprintf("%v", value)}
|
||||
}
|
||||
case *string:
|
||||
ret[k] = []string{*t}
|
||||
case string:
|
||||
ret[k] = []string{t}
|
||||
default:
|
||||
panic("unhandled")
|
||||
}
|
||||
}
|
||||
|
||||
var body []string
|
||||
var reader io.Reader
|
||||
for len(ret.Encode()) > 2048 {
|
||||
for s, i := range ret {
|
||||
ret.Del(s)
|
||||
body = append(body, s+"="+i[0])
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(body) > 0 {
|
||||
reader = bytes.NewReader([]byte(strings.Join(body, "&")))
|
||||
}
|
||||
|
||||
return ret, reader, nil
|
||||
}
|
||||
|
||||
|
||||
func UnmarshalURLValues(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] = value
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// EncodeObjectValue e.g. `idSKU_123456` = map["id"]="SKU_123456"
|
||||
func EncodeObjectValue(s map[string]any) string {
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
keys := make([]string, 0, len(s))
|
||||
for k := range s {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
var ret []string
|
||||
for _, k := range keys {
|
||||
ret = append(ret, k+fmt.Sprintf("%s", s[k]))
|
||||
}
|
||||
return strings.Join(ret, "~")
|
||||
}
|
||||
169
measurementprotocol/v2/unmarshal_test.go
Normal file
169
measurementprotocol/v2/unmarshal_test.go
Normal file
@ -0,0 +1,169 @@
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
mpv2 "github.com/foomo/sesamy/measurementprotocol/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
||||
func TestUnmarshalURLValues(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.UnmarshalURLValues(u, e); !assert.ErrorIs(t, err, tt.want) {
|
||||
t.Errorf("UnmarshalURLValues() = %v, want %v", err, tt.want)
|
||||
t.Log(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalURLValues(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.UnmarshalURLValues(u, e))
|
||||
if !assert.Empty(t, e.Unknown) {
|
||||
t.Errorf("MarshalURLValues() = %v, want %v", e.Unknown, nil)
|
||||
}
|
||||
if out, _, err := mpv2.Marshal(e); assert.NoError(t, err){
|
||||
assert.EqualValues(t, u, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestCollect_decodeMapValue(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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user